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