Add Handler feature for config.
[shibboleth/cpp-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         aclresult_t authorized(const SPRequest& request, const Session* session) const {\r
53             return shib_acl_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         for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {\r
336             delete i->first;\r
337             delete i->second;\r
338         }\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
341             delete j->second;\r
342         }\r
343         throw;\r
344     }\r
345 }\r
346 \r
347 Override::~Override()\r
348 {\r
349     delete m_acl;\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
352         delete i->first;\r
353         delete i->second;\r
354     }\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
357         delete j->second;\r
358     }\r
359 }\r
360 \r
361 pair<bool,bool> Override::getBool(const char* name, const char* ns) const\r
362 {\r
363     pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);\r
364     if (ret.first)\r
365         return ret;\r
366     return m_base ? m_base->getBool(name,ns) : ret;\r
367 }\r
368 \r
369 pair<bool,const char*> Override::getString(const char* name, const char* ns) const\r
370 {\r
371     pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);\r
372     if (ret.first)\r
373         return ret;\r
374     return m_base ? m_base->getString(name,ns) : ret;\r
375 }\r
376 \r
377 pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const\r
378 {\r
379     pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);\r
380     if (ret.first)\r
381         return ret;\r
382     return m_base ? m_base->getXMLString(name,ns) : ret;\r
383 }\r
384 \r
385 pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const\r
386 {\r
387     pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);\r
388     if (ret.first)\r
389         return ret;\r
390     return m_base ? m_base->getUnsignedInt(name,ns) : ret;\r
391 }\r
392 \r
393 pair<bool,int> Override::getInt(const char* name, const char* ns) const\r
394 {\r
395     pair<bool,int> ret=DOMPropertySet::getInt(name,ns);\r
396     if (ret.first)\r
397         return ret;\r
398     return m_base ? m_base->getInt(name,ns) : ret;\r
399 }\r
400 \r
401 const PropertySet* Override::getPropertySet(const char* name, const char* ns) const\r
402 {\r
403     const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);\r
404     if (ret || !m_base)\r
405         return ret;\r
406     return m_base->getPropertySet(name,ns);\r
407 }\r
408 \r
409 const Override* Override::locate(const SPRequest& request) const\r
410 {\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
416     if (*path == '/')\r
417         path++;\r
418 \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
422     if (sep)\r
423         *sep=0;\r
424     for (char* pch=dup; *pch; pch++)\r
425         *pch=tolower(*pch);\r
426 \r
427     // Default is for the current object to provide settings.\r
428     const Override* o=this;\r
429 \r
430     // Tokenize the path by segment and try and map each segment.\r
431 #ifdef HAVE_STRTOK_R\r
432     char* pos=NULL;\r
433     const char* token=strtok_r(dup,"/",&pos);\r
434 #else\r
435     const char* token=strtok(dup,"/");\r
436 #endif\r
437     while (token) {\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
442         o=i->second;\r
443         \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
447         if (*path == '/')\r
448             path++;\r
449 \r
450         // Get the next segment, if any.\r
451 #ifdef HAVE_STRTOK_R\r
452         token=strtok_r(NULL,"/",&pos);\r
453 #else\r
454         token=strtok(NULL,"/");\r
455 #endif\r
456     }\r
457 \r
458     free(dup);\r
459 \r
460     // If there's anything left, we try for a regex match on the rest of the path minus the query string.\r
461     if (*path) {\r
462         string path2(path);\r
463         path2 = path2.substr(0,path2.find('?'));\r
464 \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
467                 o = re->second;\r
468                 break;\r
469             }\r
470         }\r
471     }\r
472 \r
473     // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop.\r
474     bool descended;\r
475     do {\r
476         descended = false;\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
484                             o = q->second;\r
485                             descended = true;\r
486                             break;\r
487                         }\r
488                     }\r
489                 }\r
490                 else {\r
491                     // The simple presence of the parameter is sufficient to match.\r
492                     o = q->second;\r
493                     descended = true;\r
494                 }\r
495             }\r
496         }\r
497     } while (descended);\r
498 \r
499     return o;\r
500 }\r
501 \r
502 XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)\r
503 {\r
504 #ifdef _DEBUG\r
505     xmltooling::NDC ndc("XMLRequestMapperImpl");\r
506 #endif\r
507 \r
508     // Load the property set.\r
509     load(e,log,this);\r
510     \r
511     // Load any AccessControl provider.\r
512     loadACL(e,log);\r
513 \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
518         if (!n || !*n) {\r
519             log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);\r
520             continue;\r
521         }\r
522 \r
523         auto_ptr<Override> o(new Override(host,log,this));\r
524 \r
525         const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);\r
526         try {\r
527             auto_ptr<RegularExpression> re(\r
528                 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)\r
529                 );\r
530             m_regexps.push_back(make_pair(re.release(), o.release()));\r
531         }\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
535         }\r
536 \r
537         log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);\r
538     }\r
539 \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
544         if (!n || !*n) {\r
545             log.warn("Skipping Host element (%d) with empty name attribute",i);\r
546             continue;\r
547         }\r
548         \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
553         \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
558 \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
562         }\r
563         else if (scheme.first && !port.first) {\r
564             // Scheme, no port, so default it.\r
565             // XXX Use getservbyname instead?\r
566             port.first = true;\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
577         }\r
578 \r
579         if (scheme.first) {\r
580             string url(scheme.second);\r
581             url=url + "://" + dup;\r
582             \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
592                     delete o;\r
593                     continue;\r
594                 }\r
595                 m_map[url]=o;\r
596                 log.debug("Added <Host> mapping for %s",url.c_str());\r
597                 \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
600                 m_extras[url]=o;\r
601                 log.debug("Added <Host> mapping for %s",url.c_str());\r
602             }\r
603             else {\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
607                     delete o;\r
608                     continue;\r
609                 }\r
610                 m_map[url]=o;\r
611                 log.debug("Added <Host> mapping for %s",url.c_str());\r
612             }\r
613         }\r
614         else {\r
615             // No scheme or port, so we enter dual hosts on http:80 and https:443\r
616             string url("http://");\r
617             url = url + dup;\r
618             if (m_map.count(url) || m_extras.count(url)) {\r
619                 log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
620                 delete o;\r
621                 continue;\r
622             }\r
623             m_map[url]=o;\r
624             log.debug("Added <Host> mapping for %s",url.c_str());\r
625             \r
626             url = url + ":80";\r
627             if (m_map.count(url) || m_extras.count(url)) {\r
628                 log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
629                 continue;\r
630             }\r
631             m_extras[url]=o;\r
632             log.debug("Added <Host> mapping for %s",url.c_str());\r
633             \r
634             url = "https://";\r
635             url = url + dup;\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             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
646                 continue;\r
647             }\r
648             m_extras[url]=o;\r
649             log.debug("Added <Host> mapping for %s",url.c_str());\r
650         }\r
651     }\r
652 }\r
653 \r
654 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const SPRequest& request) const\r
655 {\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
659         o=i->second;\r
660     else {\r
661         i=m_extras.find(vhost);\r
662         if (i!=m_extras.end())\r
663             o=i->second;\r
664         else {\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
667                     o=re->second;\r
668             }\r
669         }\r
670     }\r
671     \r
672     return o ? o->locate(request) : this;\r
673 }\r
674 \r
675 pair<bool,DOMElement*> XMLRequestMapper::load()\r
676 {\r
677     // Load from source using base class.\r
678     pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
679     \r
680     // If we own it, wrap it.\r
681     XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
682 \r
683     XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);\r
684     \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
687 \r
688     delete m_impl;\r
689     m_impl = impl;\r
690 \r
691     return make_pair(false,(DOMElement*)NULL);\r
692 }\r
693 \r
694 RequestMapper::Settings XMLRequestMapper::getSettings(const SPRequest& request) const\r
695 {\r
696     ostringstream vhost;\r
697     vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();\r
698 \r
699     const Override* o=m_impl->findOverride(vhost.str().c_str(), request);\r
700 \r
701     if (m_log.isDebugEnabled()) {\r
702 #ifdef _DEBUG\r
703         xmltooling::NDC ndc("getSettings");\r
704 #endif\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
707     }\r
708 \r
709     return Settings(o,o->getAC());\r
710 }\r