Boost code changes
[shibboleth/cpp-sp.git] / shibsp / impl / XMLRequestMapper.cpp
1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20
21 /** XMLRequestMapper.cpp
22  *
23  * XML-based RequestMapper implementation.
24  */
25
26 #include "internal.h"
27 #include "exceptions.h"
28 #include "AccessControl.h"
29 #include "RequestMapper.h"
30 #include "SPRequest.h"
31 #include "util/CGIParser.h"
32 #include "util/DOMPropertySet.h"
33 #include "util/SPConstants.h"
34
35 #include <algorithm>
36 #include <boost/scoped_ptr.hpp>
37 #include <boost/shared_ptr.hpp>
38 #include <boost/lexical_cast.hpp>
39 #include <boost/tokenizer.hpp>
40 #include <boost/tuple/tuple.hpp>
41 #include <boost/algorithm/string.hpp>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/ReloadableXMLFile.h>
44 #include <xmltooling/util/Threads.h>
45 #include <xmltooling/util/XMLHelper.h>
46 #include <xercesc/util/XMLUniDefs.hpp>
47 #include <xercesc/util/regx/RegularExpression.hpp>
48
49 using shibspconstants::SHIB2SPCONFIG_NS;
50 using namespace shibsp;
51 using namespace xmltooling;
52 using namespace boost;
53 using namespace std;
54
55 namespace shibsp {
56
57     // Blocks access when an ACL plugin fails to load.
58     class AccessControlDummy : public AccessControl
59     {
60     public:
61         Lockable* lock() {
62             return this;
63         }
64
65         void unlock() {}
66
67         aclresult_t authorized(const SPRequest& request, const Session* session) const {
68             return shib_acl_false;
69         }
70     };
71
72     class Override : public DOMPropertySet, public DOMNodeFilter
73     {
74     public:
75         Override(bool unicodeAware=false) : m_unicodeAware(unicodeAware) {}
76         Override(bool unicodeAware, const DOMElement* e, Category& log, const Override* base=nullptr);
77         ~Override() {}
78
79         // Provides filter to exclude special config elements.
80 #ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
81         short
82 #else
83         FilterAction
84 #endif
85         acceptNode(const DOMNode* node) const {
86             return FILTER_REJECT;
87         }
88
89         const Override* locate(const HTTPRequest& request) const;
90         AccessControl* getAC() const { return (m_acl ? m_acl.get() : (getParent() ? dynamic_cast<const Override*>(getParent())->getAC() : nullptr)); }
91
92     protected:
93         void loadACL(const DOMElement* e, Category& log);
94
95         bool m_unicodeAware;
96         map< string,boost::shared_ptr<Override> > m_map;
97         vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > > m_regexps;
98         vector< tuple< string,boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > > m_queries;
99
100     private:
101         scoped_ptr<AccessControl> m_acl;
102     };
103
104     class XMLRequestMapperImpl : public Override
105     {
106     public:
107         XMLRequestMapperImpl(const DOMElement* e, Category& log);
108
109         ~XMLRequestMapperImpl() {
110             if (m_document)
111                 m_document->release();
112         }
113
114         void setDocument(DOMDocument* doc) {
115             m_document = doc;
116         }
117
118         const Override* findOverride(const char* vhost, const HTTPRequest& request) const;
119
120     private:
121         DOMDocument* m_document;
122     };
123
124 #if defined (_MSC_VER)
125     #pragma warning( push )
126     #pragma warning( disable : 4250 )
127 #endif
128
129     class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile
130     {
131     public:
132         XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e,Category::getInstance(SHIBSP_LOGCAT".RequestMapper")) {
133             background_load();
134         }
135
136         ~XMLRequestMapper() {
137             shutdown();
138         }
139
140         Settings getSettings(const HTTPRequest& request) const;
141
142     protected:
143         pair<bool,DOMElement*> background_load();
144
145     private:
146         scoped_ptr<XMLRequestMapperImpl> m_impl;
147     };
148
149 #if defined (_MSC_VER)
150     #pragma warning( pop )
151 #endif
152
153     RequestMapper* SHIBSP_DLLLOCAL XMLRequestMapperFactory(const DOMElement* const & e)
154     {
155         return new XMLRequestMapper(e);
156     }
157
158     static const XMLCh _AccessControl[] =           UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
159     static const XMLCh AccessControlProvider[] =    UNICODE_LITERAL_21(A,c,c,e,s,s,C,o,n,t,r,o,l,P,r,o,v,i,d,e,r);
160     static const XMLCh Host[] =                     UNICODE_LITERAL_4(H,o,s,t);
161     static const XMLCh HostRegex[] =                UNICODE_LITERAL_9(H,o,s,t,R,e,g,e,x);
162     static const XMLCh htaccess[] =                 UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
163     static const XMLCh ignoreCase[] =               UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);
164     static const XMLCh ignoreOption[] =             UNICODE_LITERAL_1(i);
165     static const XMLCh Path[] =                     UNICODE_LITERAL_4(P,a,t,h);
166     static const XMLCh PathRegex[] =                UNICODE_LITERAL_9(P,a,t,h,R,e,g,e,x);
167     static const XMLCh Query[] =                    UNICODE_LITERAL_5(Q,u,e,r,y);
168     static const XMLCh name[] =                     UNICODE_LITERAL_4(n,a,m,e);
169     static const XMLCh regex[] =                    UNICODE_LITERAL_5(r,e,g,e,x);
170     static const XMLCh _type[] =                    UNICODE_LITERAL_4(t,y,p,e);
171 }
172
173 void SHIBSP_API shibsp::registerRequestMappers()
174 {
175     SPConfig& conf=SPConfig::getConfig();
176     conf.RequestMapperManager.registerFactory(XML_REQUEST_MAPPER, XMLRequestMapperFactory);
177     conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
178 }
179
180 RequestMapper::RequestMapper()
181 {
182 }
183
184 RequestMapper::~RequestMapper()
185 {
186 }
187
188 void Override::loadACL(const DOMElement* e, Category& log)
189 {
190     try {
191         const DOMElement* acl = XMLHelper::getFirstChildElement(e,htaccess);
192         if (acl) {
193             log.info("building Apache htaccess AccessControl provider...");
194             m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl));
195         }
196         else {
197             acl = XMLHelper::getFirstChildElement(e,_AccessControl);
198             if (acl) {
199                 log.info("building XML-based AccessControl provider...");
200                 m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl));
201             }
202             else {
203                 acl = XMLHelper::getFirstChildElement(e,AccessControlProvider);
204                 if (acl) {
205                     string t(XMLHelper::getAttrString(acl, nullptr, _type));
206                     if (!t.empty()) {
207                         log.info("building AccessControl provider of type %s...", t.c_str());
208                         m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acl));
209                     }
210                     else {
211                         throw ConfigurationException("<AccessControlProvider> missing type attribute.");
212                     }
213                 }
214             }
215         }
216     }
217     catch (std::exception& ex) {
218         log.crit("exception building AccessControl provider: %s", ex.what());
219         m_acl.reset(new AccessControlDummy());
220     }
221 }
222
223 Override::Override(bool unicodeAware, const DOMElement* e, Category& log, const Override* base)
224     : m_unicodeAware(unicodeAware)
225 {
226     // Load the property set.
227     load(e, nullptr, this);
228     setParent(base);
229
230     // Load any AccessControl provider.
231     loadACL(e, log);
232
233     // Handle nested Paths.
234     DOMElement* path = XMLHelper::getFirstChildElement(e, Path);
235     for (int i = 1; path; ++i, path = XMLHelper::getNextSiblingElement(path, Path)) {
236         const XMLCh* n = path->getAttributeNS(nullptr,name);
237
238         // Skip any leading slashes.
239         while (n && *n == chForwardSlash)
240             n++;
241
242         // Check for empty name.
243         if (!n || !*n) {
244             log.warn("skipping Path element (%d) with empty name attribute", i);
245             continue;
246         }
247
248         // Check for an embedded slash.
249         int slash = XMLString::indexOf(n, chForwardSlash);
250         if (slash > 0) {
251             // Copy the first path segment.
252             xstring namebuf;
253             for (int pos = 0; pos < slash; ++pos)
254                 namebuf += n[pos];
255
256             // Move past the slash in the original pathname.
257             n = n + slash + 1;
258
259             // Skip any leading slashes again.
260             while (*n == chForwardSlash)
261                 ++n;
262
263             if (*n) {
264                 // Create a placeholder Path element for the first path segment and replant under it.
265                 DOMElement* newpath = path->getOwnerDocument()->createElementNS(shibspconstants::SHIB2SPCONFIG_NS, Path);
266                 newpath->setAttributeNS(nullptr, name, namebuf.c_str());
267                 path->setAttributeNS(nullptr, name, n);
268                 path->getParentNode()->replaceChild(newpath, path);
269                 newpath->appendChild(path);
270
271                 // Repoint our locals at the new parent.
272                 path = newpath;
273                 n = path->getAttributeNS(nullptr, name);
274             }
275             else {
276                 // All we had was a pathname with trailing slash(es), so just reset it without them.
277                 path->setAttributeNS(nullptr, name, namebuf.c_str());
278                 n = path->getAttributeNS(nullptr, name);
279             }
280         }
281
282         char* dup = nullptr;
283         try {
284             boost::shared_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
285             if (m_unicodeAware) {
286                 dup = toUTF8(o->getXMLString("name").second, true /* use malloc */);
287             }
288             else {
289                 dup = strdup(o->getString("name").second);
290                 for (char* pch = dup; *pch; ++pch)
291                     *pch = tolower(*pch);
292             }
293             if (m_map.count(dup)) {
294                 log.warn("skipping duplicate Path element (%s)", dup);
295             }
296             else {
297                 m_map[dup] = o;
298                 log.debug("added Path mapping (%s)", dup);
299             }
300             free(dup);
301         }
302         catch (std::exception&) {
303             free(dup);
304             throw;
305         }
306     }
307
308     if (!XMLString::equals(e->getLocalName(), PathRegex)) {
309         // Handle nested PathRegexs.
310         path = XMLHelper::getFirstChildElement(e, PathRegex);
311         for (int i = 1; path; ++i, path = XMLHelper::getNextSiblingElement(path, PathRegex)) {
312             const XMLCh* n = path->getAttributeNS(nullptr, regex);
313             if (!n || !*n) {
314                 log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
315                 continue;
316             }
317
318             boost::shared_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
319
320             bool flag = XMLHelper::getAttrBool(path, true, ignoreCase);
321             try {
322                 boost::shared_ptr<RegularExpression> re(new RegularExpression(n, flag ? &chNull : ignoreOption));
323                 m_regexps.push_back(make_pair(re, o));
324             }
325             catch (XMLException& ex) {
326                 auto_ptr_char tmp(ex.getMessage());
327                 log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
328                 throw ConfigurationException("Invalid regular expression in PathRegex element.");
329             }
330
331             if (log.isDebugEnabled())
332                 log.debug("added <PathRegex> mapping (%s)", o->getString("regex").second);
333         }
334     }
335
336     // Handle nested Querys.
337     path = XMLHelper::getFirstChildElement(e, Query);
338     for (int i = 1; path; ++i, path = XMLHelper::getNextSiblingElement(path, Query)) {
339         const XMLCh* n = path->getAttributeNS(nullptr, name);
340         if (!n || !*n) {
341             log.warn("skipping Query element (%d) with empty name attribute",i);
342             continue;
343         }
344         auto_ptr_char ntemp(n);
345         const XMLCh* v = path->getAttributeNS(nullptr, regex);
346
347         try {
348             boost::shared_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
349             boost::shared_ptr<RegularExpression> re((v && *v) ? new RegularExpression(v) : nullptr);
350             m_queries.push_back(make_tuple(string(ntemp.get()), re, o));
351         }
352         catch (XMLException& ex) {
353             auto_ptr_char tmp(ex.getMessage());
354             log.error("caught exception while parsing Query regular expression (%d): %s", i, tmp.get());
355             throw ConfigurationException("Invalid regular expression in Query element.");
356         }
357
358         log.debug("added <Query> mapping (%s)", ntemp.get());
359     }
360 }
361
362 const Override* Override::locate(const HTTPRequest& request) const
363 {
364     // This function is confusing because it's *not* recursive.
365     // The whole path is tokenized and mapped in a loop, so the
366     // path parameter starts with the entire request path and
367     // we can skip the leading slash as irrelevant.
368     const char* path = request.getRequestURI();
369     if (*path == '/')
370         path++;
371
372     // Now we copy the path, chop the query string, and possibly lower case it.
373     string dup(path);
374     string::size_type sep = dup.find('?');
375     if (sep != string::npos)
376         dup = dup.substr(0, sep);
377     if (!m_unicodeAware) {
378         to_lower(dup);
379     }
380
381     // Default is for the current object to provide settings.
382     const Override* o = this;
383
384     // Tokenize the path by segment and try and map each segment.
385     tokenizer< char_separator<char> > tokens(dup, char_separator<char>("/"));
386     for (tokenizer< char_separator<char> >::iterator token = tokens.begin(); token != tokens.end(); ++token) {
387         map< string,boost::shared_ptr<Override> >::const_iterator i = o->m_map.find(*token);
388         if (i == o->m_map.end())
389             break;  // Once there's no match, we've consumed as much of the path as possible here.
390         // We found a match, so reset the settings pointer.
391         o = i->second.get();
392
393         // We descended a step down the path, so we need to advance the original
394         // parameter for the regex step later.
395         path += token->length();
396         if (*path == '/')
397             path++;
398     }
399
400     // If there's anything left, we try for a regex match on the rest of the path minus the query string.
401     if (*path) {
402         string path2(path);
403         sep = path2.find('?');
404         if (sep != string::npos)
405             path2 = path2.substr(0, sep);
406
407         for (vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
408             if (re->first->matches(path2.c_str())) {
409                 o = re->second.get();
410                 break;
411             }
412         }
413     }
414
415     // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop.
416     // To avoid consuming any POST data, we use a dedicated CGIParser.
417     if (!o->m_queries.empty()) {
418         bool descended;
419         CGIParser cgi(request, true);
420         do {
421             descended = false;
422             for (vector< tuple< string,boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) {
423                 pair<CGIParser::walker,CGIParser::walker> vals = cgi.getParameters(q->get<0>().c_str());
424                 if (vals.first != vals.second) {
425                     if (q->get<1>()) {
426                         // We have to match one of the values.
427                         while (vals.first != vals.second) {
428                             if (q->get<1>()->matches(vals.first->second)) {
429                                 o = q->get<2>().get();
430                                 descended = true;
431                                 break;
432                             }
433                             ++vals.first;
434                         }
435                     }
436                     else {
437                         // The simple presence of the parameter is sufficient to match.
438                         o = q->get<2>().get();
439                         descended = true;
440                     }
441                 }
442             }
443         } while (descended);
444     }
445
446     return o;
447 }
448
449 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(nullptr)
450 {
451 #ifdef _DEBUG
452     xmltooling::NDC ndc("XMLRequestMapperImpl");
453 #endif
454     static const XMLCh _RequestMap[] =  UNICODE_LITERAL_10(R,e,q,u,e,s,t,M,a,p);
455
456     if (e && !XMLHelper::isNodeNamed(e, SHIB2SPCONFIG_NS, _RequestMap))
457         throw ConfigurationException("XML RequestMapper requires conf:RequestMap at root of configuration.");
458
459     // Load the property set.
460     load(e, nullptr, this);
461
462     // Inject "default" app ID if not explicit.
463     if (!getString("applicationId").first)
464         setProperty("applicationId", "default");
465
466     // Load any AccessControl provider.
467     loadACL(e, log);
468
469     pair<bool,bool> unicodeAware = getBool("unicodeAware");
470     m_unicodeAware = (unicodeAware.first && unicodeAware.second);
471
472     // Loop over the HostRegex elements.
473     const DOMElement* host = XMLHelper::getFirstChildElement(e, HostRegex);
474     for (int i = 1; host; ++i, host = XMLHelper::getNextSiblingElement(host, HostRegex)) {
475         const XMLCh* n = host->getAttributeNS(nullptr,regex);
476         if (!n || !*n) {
477             log.warn("Skipping HostRegex element (%d) with empty regex attribute", i);
478             continue;
479         }
480
481         boost::shared_ptr<Override> o(new Override(m_unicodeAware, host, log, this));
482
483         const XMLCh* flag = host->getAttributeNS(nullptr,ignoreCase);
484         try {
485             boost::shared_ptr<RegularExpression> re(
486                 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
487                 );
488             m_regexps.push_back(make_pair(re, o));
489         }
490         catch (XMLException& ex) {
491             auto_ptr_char tmp(ex.getMessage());
492             log.error("caught exception while parsing HostRegex regular expression (%d): %s", i, tmp.get());
493         }
494
495         log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);
496     }
497
498     // Loop over the Host elements.
499     host = XMLHelper::getFirstChildElement(e, Host);
500     for (int i = 1; host; ++i, host = XMLHelper::getNextSiblingElement(host, Host)) {
501         const XMLCh* n=host->getAttributeNS(nullptr,name);
502         if (!n || !*n) {
503             log.warn("Skipping Host element (%d) with empty name attribute", i);
504             continue;
505         }
506
507         boost::shared_ptr<Override> o(new Override(m_unicodeAware, host, log, this));
508         pair<bool,const char*> name=o->getString("name");
509         pair<bool,const char*> scheme=o->getString("scheme");
510         pair<bool,const char*> port=o->getString("port");
511
512         string dup(name.first ? name.second : "");
513         to_lower(dup);
514
515         if (!scheme.first && port.first) {
516             // No scheme, but a port, so assume http.
517             scheme = pair<bool,const char*>(true,"http");
518         }
519         else if (scheme.first && !port.first) {
520             // Scheme, no port, so default it.
521             // XXX Use getservbyname instead?
522             port.first = true;
523             if (!strcmp(scheme.second,"http"))
524                 port.second = "80";
525             else if (!strcmp(scheme.second,"https"))
526                 port.second = "443";
527             else if (!strcmp(scheme.second,"ftp"))
528                 port.second = "21";
529             else if (!strcmp(scheme.second,"ldap"))
530                 port.second = "389";
531             else if (!strcmp(scheme.second,"ldaps"))
532                 port.second = "636";
533         }
534
535         if (scheme.first) {
536             string url(scheme.second);
537             url=url + "://" + dup;
538
539             // Is this the default port?
540             if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
541                 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
542                 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
543                 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
544                 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
545                 // First store a port-less version.
546                 if (m_map.count(url)) {
547                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
548                     continue;
549                 }
550                 m_map[url] = o;
551                 log.debug("Added <Host> mapping for %s",url.c_str());
552
553                 // Now append the port. The shared_ptr should refcount the Override to avoid double deletes.
554                 url=url + ':' + port.second;
555                 m_map[url] = o;
556                 log.debug("Added <Host> mapping for %s",url.c_str());
557             }
558             else {
559                 url=url + ':' + port.second;
560                 if (m_map.count(url)) {
561                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
562                     continue;
563                 }
564                 m_map[url] = o;
565                 log.debug("Added <Host> mapping for %s",url.c_str());
566             }
567         }
568         else {
569             // No scheme or port, so we enter dual hosts on http:80 and https:443
570             string url("http://");
571             url += dup;
572             if (m_map.count(url)) {
573                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
574                 continue;
575             }
576             m_map[url] = o;
577             log.debug("Added <Host> mapping for %s",url.c_str());
578
579             url += ":80";
580             if (m_map.count(url)) {
581                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
582                 continue;
583             }
584             m_map[url] = o;
585             log.debug("Added <Host> mapping for %s",url.c_str());
586
587             url = "https://" + dup;
588             if (m_map.count(url)) {
589                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
590                 continue;
591             }
592             m_map[url] = o;
593             log.debug("Added <Host> mapping for %s",url.c_str());
594
595             url += ":443";
596             if (m_map.count(url)) {
597                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
598                 continue;
599             }
600             m_map[url] = o;
601             log.debug("Added <Host> mapping for %s",url.c_str());
602         }
603     }
604 }
605
606 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const
607 {
608     const Override* o = nullptr;
609     map< string,boost::shared_ptr<Override> >::const_iterator i = m_map.find(vhost);
610     if (i != m_map.end())
611         o = i->second.get();
612     else {
613         for (vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) {
614             if (re->first->matches(vhost))
615                 o=re->second.get();
616         }
617     }
618
619     return o ? o->locate(request) : this;
620 }
621
622 pair<bool,DOMElement*> XMLRequestMapper::background_load()
623 {
624     // Load from source using base class.
625     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
626
627     // If we own it, wrap it.
628     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
629
630     scoped_ptr<XMLRequestMapperImpl> impl(new XMLRequestMapperImpl(raw.second, m_log));
631
632     // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
633     impl->setDocument(docjanitor.release());
634
635     // Perform the swap inside a lock.
636     if (m_lock)
637         m_lock->wrlock();
638     SharedLock locker(m_lock, false);
639     m_impl.swap(impl);
640
641     return make_pair(false,(DOMElement*)nullptr);
642 }
643
644 RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const
645 {
646     try {
647         string vhost = string(request.getScheme()) + "://" + request.getHostname() + ':' + lexical_cast<string>(request.getPort());
648         const Override* o=m_impl->findOverride(vhost.c_str(), request);
649         return Settings(o, o->getAC());
650     }
651     catch (XMLException& ex) {
652         auto_ptr_char tmp(ex.getMessage());
653         m_log.error("caught exception while locating content settings: %s", tmp.get());
654         throw ConfigurationException("XML-based RequestMapper failed to retrieve content settings.");
655     }
656 }