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