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