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