2 * Copyright 2001-2007 Internet2
\r
4 * Licensed under the Apache License, Version 2.0 (the "License");
\r
5 * you may not use this file except in compliance with the License.
\r
6 * You may obtain a copy of the License at
\r
8 * http://www.apache.org/licenses/LICENSE-2.0
\r
10 * Unless required by applicable law or agreed to in writing, software
\r
11 * distributed under the License is distributed on an "AS IS" BASIS,
\r
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
\r
13 * See the License for the specific language governing permissions and
\r
14 * limitations under the License.
\r
17 /** XMLRequestMapper.cpp
\r
19 * XML-based RequestMapper implementation
\r
22 #include "internal.h"
\r
23 #include "exceptions.h"
\r
24 #include "AccessControl.h"
\r
25 #include "RequestMapper.h"
\r
26 #include "SPRequest.h"
\r
27 #include "util/DOMPropertySet.h"
\r
28 #include "util/SPConstants.h"
\r
30 #include <xmltooling/util/NDC.h>
\r
31 #include <xmltooling/util/ReloadableXMLFile.h>
\r
32 #include <xmltooling/util/XMLHelper.h>
\r
33 #include <xercesc/util/XMLUniDefs.hpp>
\r
34 #include <xercesc/util/regx/RegularExpression.hpp>
\r
36 using namespace shibsp;
\r
37 using namespace xmltooling;
\r
38 using namespace std;
\r
42 // Blocks access when an ACL plugin fails to load.
\r
43 class AccessControlDummy : public AccessControl
\r
52 aclresult_t authorized(const SPRequest& request, const Session* session) const {
\r
53 return shib_acl_false;
\r
57 class Override : public DOMPropertySet, public DOMNodeFilter
\r
60 Override() : m_base(NULL), m_acl(NULL) {}
\r
61 Override(const DOMElement* e, Category& log, const Override* base=NULL);
\r
65 pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;
\r
66 pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;
\r
67 pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;
\r
68 pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;
\r
69 pair<bool,int> getInt(const char* name, const char* ns=NULL) const;
\r
70 const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:2.0:native:sp:config") const;
\r
72 // Provides filter to exclude special config elements.
\r
73 short acceptNode(const DOMNode* node) const {
\r
74 return FILTER_REJECT;
\r
77 const Override* locate(const SPRequest& request) const;
\r
78 AccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); }
\r
81 void loadACL(const DOMElement* e, Category& log);
\r
83 map<string,Override*> m_map;
\r
84 vector< pair<RegularExpression*,Override*> > m_regexps;
\r
85 vector< pair< pair<string,RegularExpression*>,Override*> > m_queries;
\r
88 const Override* m_base;
\r
89 AccessControl* m_acl;
\r
92 class XMLRequestMapperImpl : public Override
\r
95 XMLRequestMapperImpl(const DOMElement* e, Category& log);
\r
97 ~XMLRequestMapperImpl() {
\r
99 m_document->release();
\r
102 void setDocument(DOMDocument* doc) {
\r
106 const Override* findOverride(const char* vhost, const SPRequest& request) const;
\r
109 map<string,Override*> m_extras;
\r
110 DOMDocument* m_document;
\r
113 #if defined (_MSC_VER)
\r
114 #pragma warning( push )
\r
115 #pragma warning( disable : 4250 )
\r
118 class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile
\r
121 XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e,Category::getInstance(SHIBSP_LOGCAT".RequestMapper")), m_impl(NULL) {
\r
125 ~XMLRequestMapper() {
\r
129 Settings getSettings(const SPRequest& request) const;
\r
132 pair<bool,DOMElement*> load();
\r
135 XMLRequestMapperImpl* m_impl;
\r
138 #if defined (_MSC_VER)
\r
139 #pragma warning( pop )
\r
142 RequestMapper* SHIBSP_DLLLOCAL XMLRequestMapperFactory(const DOMElement* const & e)
\r
144 return new XMLRequestMapper(e);
\r
147 static const XMLCh _AccessControl[] = UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
\r
148 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);
\r
149 static const XMLCh Host[] = UNICODE_LITERAL_4(H,o,s,t);
\r
150 static const XMLCh HostRegex[] = UNICODE_LITERAL_9(H,o,s,t,R,e,g,e,x);
\r
151 static const XMLCh htaccess[] = UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
\r
152 static const XMLCh ignoreCase[] = UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);
\r
153 static const XMLCh ignoreOption[] = UNICODE_LITERAL_1(i);
\r
154 static const XMLCh Path[] = UNICODE_LITERAL_4(P,a,t,h);
\r
155 static const XMLCh PathRegex[] = UNICODE_LITERAL_9(P,a,t,h,R,e,g,e,x);
\r
156 static const XMLCh Query[] = UNICODE_LITERAL_5(Q,u,e,r,y);
\r
157 static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e);
\r
158 static const XMLCh regex[] = UNICODE_LITERAL_5(r,e,g,e,x);
\r
159 static const XMLCh _type[] = UNICODE_LITERAL_4(t,y,p,e);
\r
162 void SHIBSP_API shibsp::registerRequestMappers()
\r
164 SPConfig& conf=SPConfig::getConfig();
\r
165 conf.RequestMapperManager.registerFactory(XML_REQUEST_MAPPER, XMLRequestMapperFactory);
\r
166 conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
\r
169 void Override::loadACL(const DOMElement* e, Category& log)
\r
172 const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
\r
174 log.info("building Apache htaccess AccessControl provider...");
\r
175 m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl);
\r
178 acl=XMLHelper::getFirstChildElement(e,_AccessControl);
\r
180 log.info("building XML-based AccessControl provider...");
\r
181 m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl);
\r
184 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
\r
186 auto_ptr_char type(acl->getAttributeNS(NULL,_type));
\r
187 log.info("building AccessControl provider of type %s...",type.get());
\r
188 m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(type.get(),acl);
\r
193 catch (exception& ex) {
\r
194 log.crit("exception building AccessControl provider: %s", ex.what());
\r
195 m_acl = new AccessControlDummy();
\r
199 Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)
\r
202 // Load the property set.
\r
205 // Load any AccessControl provider.
\r
208 // Handle nested Paths.
\r
209 DOMElement* path = XMLHelper::getFirstChildElement(e,Path);
\r
210 for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {
\r
211 const XMLCh* n=path->getAttributeNS(NULL,name);
\r
213 // Skip any leading slashes.
\r
214 while (n && *n==chForwardSlash)
\r
217 // Check for empty name.
\r
219 log.warn("skipping Path element (%d) with empty name attribute", i);
\r
223 // Check for an embedded slash.
\r
224 int slash=XMLString::indexOf(n,chForwardSlash);
\r
226 // Copy the first path segment.
\r
227 XMLCh* namebuf=new XMLCh[slash + 1];
\r
228 for (int pos=0; pos < slash; pos++)
\r
229 namebuf[pos]=n[pos];
\r
230 namebuf[slash]=chNull;
\r
232 // Move past the slash in the original pathname.
\r
235 // Skip any leading slashes again.
\r
236 while (*n==chForwardSlash)
\r
240 // Create a placeholder Path element for the first path segment and replant under it.
\r
241 DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibspconstants::SHIB2SPCONFIG_NS,Path);
\r
242 newpath->setAttributeNS(NULL,name,namebuf);
\r
243 path->setAttributeNS(NULL,name,n);
\r
244 path->getParentNode()->replaceChild(newpath,path);
\r
245 newpath->appendChild(path);
\r
247 // Repoint our locals at the new parent.
\r
249 n=path->getAttributeNS(NULL,name);
\r
252 // All we had was a pathname with trailing slash(es), so just reset it without them.
\r
253 path->setAttributeNS(NULL,name,namebuf);
\r
254 n=path->getAttributeNS(NULL,name);
\r
259 Override* o=new Override(path,log,this);
\r
260 pair<bool,const char*> name=o->getString("name");
\r
261 char* dup=strdup(name.second);
\r
262 for (char* pch=dup; *pch; pch++)
\r
263 *pch=tolower(*pch);
\r
264 if (m_map.count(dup)) {
\r
265 log.warn("skipping duplicate Path element (%s)",dup);
\r
271 log.debug("added Path mapping (%s)", dup);
\r
275 if (!XMLString::equals(e->getLocalName(), PathRegex)) {
\r
276 // Handle nested PathRegexs.
\r
277 path = XMLHelper::getFirstChildElement(e,PathRegex);
\r
278 for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,PathRegex)) {
\r
279 const XMLCh* n=path->getAttributeNS(NULL,regex);
\r
281 log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
\r
285 auto_ptr<Override> o(new Override(path,log,this));
\r
287 const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);
\r
289 auto_ptr<RegularExpression> re(
\r
290 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
\r
292 m_regexps.push_back(make_pair(re.release(), o.release()));
\r
294 catch (XMLException& ex) {
\r
295 auto_ptr_char tmp(ex.getMessage());
\r
296 log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
\r
297 throw ConfigurationException("Invalid regular expression in PathRegex element.");
\r
300 if (log.isDebugEnabled())
\r
301 log.debug("added <PathRegex> mapping (%s)", m_regexps.back().second->getString("regex").second);
\r
305 // Handle nested Querys.
\r
306 path = XMLHelper::getFirstChildElement(e,Query);
\r
307 for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Query)) {
\r
308 const XMLCh* n=path->getAttributeNS(NULL,name);
\r
310 log.warn("skipping Query element (%d) with empty name attribute",i);
\r
313 auto_ptr_char ntemp(n);
\r
314 const XMLCh* v=path->getAttributeNS(NULL,regex);
\r
316 auto_ptr<Override> o(new Override(path,log,this));
\r
318 RegularExpression* re = NULL;
\r
320 re = new RegularExpression(v);
\r
321 m_queries.push_back(make_pair(make_pair(ntemp.get(),re), o.release()));
\r
323 catch (XMLException& ex) {
\r
324 auto_ptr_char tmp(ex.getMessage());
\r
325 log.error("caught exception while parsing Query regular expression (%d): %s", i, tmp.get());
\r
326 throw ConfigurationException("Invalid regular expression in Query element.");
\r
329 log.debug("added <Query> mapping (%s)", ntemp.get());
\r
332 catch (exception&) {
\r
334 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
\r
335 for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
\r
339 for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
\r
340 delete j->first.second;
\r
347 Override::~Override()
\r
350 for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
\r
351 for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
\r
355 for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
\r
356 delete j->first.second;
\r
361 pair<bool,bool> Override::getBool(const char* name, const char* ns) const
\r
363 pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);
\r
366 return m_base ? m_base->getBool(name,ns) : ret;
\r
369 pair<bool,const char*> Override::getString(const char* name, const char* ns) const
\r
371 pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);
\r
374 return m_base ? m_base->getString(name,ns) : ret;
\r
377 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const
\r
379 pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);
\r
382 return m_base ? m_base->getXMLString(name,ns) : ret;
\r
385 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const
\r
387 pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);
\r
390 return m_base ? m_base->getUnsignedInt(name,ns) : ret;
\r
393 pair<bool,int> Override::getInt(const char* name, const char* ns) const
\r
395 pair<bool,int> ret=DOMPropertySet::getInt(name,ns);
\r
398 return m_base ? m_base->getInt(name,ns) : ret;
\r
401 const PropertySet* Override::getPropertySet(const char* name, const char* ns) const
\r
403 const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);
\r
404 if (ret || !m_base)
\r
406 return m_base->getPropertySet(name,ns);
\r
409 const Override* Override::locate(const SPRequest& request) const
\r
411 // This function is confusing because it's *not* recursive.
\r
412 // The whole path is tokenized and mapped in a loop, so the
\r
413 // path parameter starts with the entire request path and
\r
414 // we can skip the leading slash as irrelevant.
\r
415 const char* path = request.getRequestURI();
\r
419 // Now we copy the path, chop the query string, and lower case it.
\r
420 char* dup=strdup(path);
\r
421 char* sep=strchr(dup,'?');
\r
424 for (char* pch=dup; *pch; pch++)
\r
425 *pch=tolower(*pch);
\r
427 // Default is for the current object to provide settings.
\r
428 const Override* o=this;
\r
430 // Tokenize the path by segment and try and map each segment.
\r
431 #ifdef HAVE_STRTOK_R
\r
433 const char* token=strtok_r(dup,"/",&pos);
\r
435 const char* token=strtok(dup,"/");
\r
438 map<string,Override*>::const_iterator i=o->m_map.find(token);
\r
439 if (i==o->m_map.end())
\r
440 break; // Once there's no match, we've consumed as much of the path as possible here.
\r
441 // We found a match, so reset the settings pointer.
\r
444 // We descended a step down the path, so we need to advance the original
\r
445 // parameter for the regex step later.
\r
446 path += strlen(token);
\r
450 // Get the next segment, if any.
\r
451 #ifdef HAVE_STRTOK_R
\r
452 token=strtok_r(NULL,"/",&pos);
\r
454 token=strtok(NULL,"/");
\r
460 // If there's anything left, we try for a regex match on the rest of the path minus the query string.
\r
462 string path2(path);
\r
463 path2 = path2.substr(0,path2.find('?'));
\r
465 for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
\r
466 if (re->first->matches(path2.c_str())) {
\r
473 // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop.
\r
477 for (vector< pair< pair<string,RegularExpression*>,Override*> >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) {
\r
478 vector<const char*> vals;
\r
479 if (request.getParameters(q->first.first.c_str(), vals)) {
\r
480 if (q->first.second) {
\r
481 // We have to match one of the values.
\r
482 for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
\r
483 if (q->first.second->matches(*v)) {
\r
491 // The simple presence of the parameter is sufficient to match.
\r
497 } while (descended);
\r
502 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
\r
505 xmltooling::NDC ndc("XMLRequestMapperImpl");
\r
508 // Load the property set.
\r
511 // Load any AccessControl provider.
\r
514 // Loop over the HostRegex elements.
\r
515 const DOMElement* host = XMLHelper::getFirstChildElement(e,HostRegex);
\r
516 for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,HostRegex)) {
\r
517 const XMLCh* n=host->getAttributeNS(NULL,regex);
\r
519 log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);
\r
523 auto_ptr<Override> o(new Override(host,log,this));
\r
525 const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);
\r
527 auto_ptr<RegularExpression> re(
\r
528 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
\r
530 m_regexps.push_back(make_pair(re.release(), o.release()));
\r
532 catch (XMLException& ex) {
\r
533 auto_ptr_char tmp(ex.getMessage());
\r
534 log.error("caught exception while parsing HostRegex regular expression (%d): %s", i, tmp.get());
\r
537 log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);
\r
540 // Loop over the Host elements.
\r
541 host = XMLHelper::getFirstChildElement(e,Host);
\r
542 for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {
\r
543 const XMLCh* n=host->getAttributeNS(NULL,name);
\r
545 log.warn("Skipping Host element (%d) with empty name attribute",i);
\r
549 Override* o=new Override(host,log,this);
\r
550 pair<bool,const char*> name=o->getString("name");
\r
551 pair<bool,const char*> scheme=o->getString("scheme");
\r
552 pair<bool,const char*> port=o->getString("port");
\r
554 char* dup=strdup(name.second);
\r
555 for (char* pch=dup; *pch; pch++)
\r
556 *pch=tolower(*pch);
\r
557 auto_ptr<char> dupwrap(dup);
\r
559 if (!scheme.first && port.first) {
\r
560 // No scheme, but a port, so assume http.
\r
561 scheme = pair<bool,const char*>(true,"http");
\r
563 else if (scheme.first && !port.first) {
\r
564 // Scheme, no port, so default it.
\r
565 // XXX Use getservbyname instead?
\r
567 if (!strcmp(scheme.second,"http"))
\r
568 port.second = "80";
\r
569 else if (!strcmp(scheme.second,"https"))
\r
570 port.second = "443";
\r
571 else if (!strcmp(scheme.second,"ftp"))
\r
572 port.second = "21";
\r
573 else if (!strcmp(scheme.second,"ldap"))
\r
574 port.second = "389";
\r
575 else if (!strcmp(scheme.second,"ldaps"))
\r
576 port.second = "636";
\r
579 if (scheme.first) {
\r
580 string url(scheme.second);
\r
581 url=url + "://" + dup;
\r
583 // Is this the default port?
\r
584 if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
\r
585 (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
\r
586 (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
\r
587 (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
\r
588 (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
\r
589 // First store a port-less version.
\r
590 if (m_map.count(url) || m_extras.count(url)) {
\r
591 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
596 log.debug("Added <Host> mapping for %s",url.c_str());
\r
598 // Now append the port. We use the extras vector, to avoid double freeing the object later.
\r
599 url=url + ':' + port.second;
\r
601 log.debug("Added <Host> mapping for %s",url.c_str());
\r
604 url=url + ':' + port.second;
\r
605 if (m_map.count(url) || m_extras.count(url)) {
\r
606 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
611 log.debug("Added <Host> mapping for %s",url.c_str());
\r
615 // No scheme or port, so we enter dual hosts on http:80 and https:443
\r
616 string url("http://");
\r
618 if (m_map.count(url) || m_extras.count(url)) {
\r
619 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
624 log.debug("Added <Host> mapping for %s",url.c_str());
\r
627 if (m_map.count(url) || m_extras.count(url)) {
\r
628 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
632 log.debug("Added <Host> mapping for %s",url.c_str());
\r
636 if (m_map.count(url) || m_extras.count(url)) {
\r
637 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
641 log.debug("Added <Host> mapping for %s",url.c_str());
\r
643 url = url + ":443";
\r
644 if (m_map.count(url) || m_extras.count(url)) {
\r
645 log.warn("Skipping duplicate Host element (%s)",url.c_str());
\r
649 log.debug("Added <Host> mapping for %s",url.c_str());
\r
654 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const SPRequest& request) const
\r
656 const Override* o=NULL;
\r
657 map<string,Override*>::const_iterator i=m_map.find(vhost);
\r
658 if (i!=m_map.end())
\r
661 i=m_extras.find(vhost);
\r
662 if (i!=m_extras.end())
\r
665 for (vector< pair<RegularExpression*,Override*> >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) {
\r
666 if (re->first->matches(vhost))
\r
672 return o ? o->locate(request) : this;
\r
675 pair<bool,DOMElement*> XMLRequestMapper::load()
\r
677 // Load from source using base class.
\r
678 pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
\r
680 // If we own it, wrap it.
\r
681 XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
\r
683 XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);
\r
685 // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
\r
686 impl->setDocument(docjanitor.release());
\r
691 return make_pair(false,(DOMElement*)NULL);
\r
694 RequestMapper::Settings XMLRequestMapper::getSettings(const SPRequest& request) const
\r
696 ostringstream vhost;
\r
697 vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();
\r
699 const Override* o=m_impl->findOverride(vhost.str().c_str(), request);
\r
701 if (m_log.isDebugEnabled()) {
\r
703 xmltooling::NDC ndc("getSettings");
\r
705 pair<bool,const char*> ret=o->getString("applicationId");
\r
706 m_log.debug("mapped %s%s to %s", vhost.str().c_str(), request.getRequestURI() ? request.getRequestURI() : "", ret.second);
\r
709 return Settings(o,o->getAC());
\r