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