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