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