Shift property set logging to its own category.
[shibboleth/sp.git] / shibsp / impl / XMLRequestMapper.cpp
index d356923..9c5f294 100644 (file)
-/*\r
- *  Copyright 2001-2007 Internet2\r
- * \r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- *     http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-/** XMLRequestMapper.cpp\r
- * \r
- * XML-based RequestMapper implementation\r
- */\r
-\r
-#include "internal.h"\r
-#include "AccessControl.h"\r
-#include "RequestMapper.h"\r
-#include "SPRequest.h"\r
-#include "util/DOMPropertySet.h"\r
-#include "util/SPConstants.h"\r
-\r
-#include <xmltooling/util/NDC.h>\r
-#include <xmltooling/util/ReloadableXMLFile.h>\r
-#include <xmltooling/util/XMLHelper.h>\r
-#include <xercesc/util/XMLUniDefs.hpp>\r
-\r
-using namespace shibsp;\r
-using namespace xmltooling;\r
-using namespace log4cpp;\r
-using namespace std;\r
-\r
-namespace shibsp {\r
-\r
-    // Blocks access when an ACL plugin fails to load. \r
-    class AccessControlDummy : public AccessControl\r
-    {\r
-    public:\r
-        Lockable* lock() {\r
-            return this;\r
-        }\r
-        \r
-        void unlock() {}\r
-    \r
-        bool authorized(const SPRequest& request, const Session* session) const {\r
-            return false;\r
-        }\r
-    };\r
-\r
-    class Override : public DOMPropertySet, public DOMNodeFilter\r
-    {\r
-    public:\r
-        Override() : m_base(NULL), m_acl(NULL) {}\r
-        Override(const DOMElement* e, Category& log, const Override* base=NULL);\r
-        ~Override();\r
-\r
-        // PropertySet\r
-        pair<bool,bool> getBool(const char* name, const char* ns=NULL) const;\r
-        pair<bool,const char*> getString(const char* name, const char* ns=NULL) const;\r
-        pair<bool,const XMLCh*> getXMLString(const char* name, const char* ns=NULL) const;\r
-        pair<bool,unsigned int> getUnsignedInt(const char* name, const char* ns=NULL) const;\r
-        pair<bool,int> getInt(const char* name, const char* ns=NULL) const;\r
-        const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const;\r
-        \r
-        // Provides filter to exclude special config elements.\r
-        short acceptNode(const DOMNode* node) const;\r
-\r
-        const Override* locate(const char* path) const;\r
-        AccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); }\r
-        \r
-    protected:\r
-        void loadACL(const DOMElement* e, Category& log);\r
-        \r
-        map<string,Override*> m_map;\r
-    \r
-    private:\r
-        const Override* m_base;\r
-        AccessControl* m_acl;\r
-    };\r
-\r
-    class XMLRequestMapperImpl : public Override\r
-    {\r
-    public:\r
-        XMLRequestMapperImpl(const DOMElement* e, Category& log);\r
-\r
-        ~XMLRequestMapperImpl() {\r
-            if (m_document)\r
-                m_document->release();\r
-        }\r
-\r
-        void setDocument(DOMDocument* doc) {\r
-            m_document = doc;\r
-        }\r
-    \r
-        const Override* findOverride(const char* vhost, const char* path) const;\r
-\r
-    private:    \r
-        map<string,Override*> m_extras;\r
-        DOMDocument* m_document;\r
-    };\r
-\r
-#if defined (_MSC_VER)\r
-    #pragma warning( push )\r
-    #pragma warning( disable : 4250 )\r
-#endif\r
-\r
-    class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile\r
-    {\r
-    public:\r
-        XMLRequestMapper(const DOMElement* e)\r
-                : ReloadableXMLFile(e), m_impl(NULL), m_log(Category::getInstance(SHIBSP_LOGCAT".RequestMapper")) {\r
-            load();\r
-        }\r
-\r
-        ~XMLRequestMapper() {\r
-            delete m_impl;\r
-        }\r
-\r
-        Settings getSettings(const SPRequest& request) const;\r
-\r
-    protected:\r
-        pair<bool,DOMElement*> load();\r
-\r
-    private:\r
-        XMLRequestMapperImpl* m_impl;\r
-        Category& m_log;\r
-    };\r
-\r
-#if defined (_MSC_VER)\r
-    #pragma warning( pop )\r
-#endif\r
-\r
-    RequestMapper* SHIBSP_DLLLOCAL XMLRequestMapperFactory(const DOMElement* const & e)\r
-    {\r
-        return new XMLRequestMapper(e);\r
-    }\r
-\r
-    static const XMLCh _AccessControl[] =            UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);\r
-    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
-    static const XMLCh htaccess[] =                 UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);\r
-    static const XMLCh Host[] =                     UNICODE_LITERAL_4(H,o,s,t);\r
-    static const XMLCh Path[] =                     UNICODE_LITERAL_4(P,a,t,h);\r
-    static const XMLCh name[] =                     UNICODE_LITERAL_4(n,a,m,e);\r
-    static const XMLCh type[] =                     UNICODE_LITERAL_4(t,y,p,e);\r
-}\r
-\r
-void SHIBSP_API shibsp::registerRequestMappers()\r
-{\r
-    SPConfig& conf=SPConfig::getConfig();\r
-    conf.RequestMapperManager.registerFactory(XML_REQUEST_MAPPER, XMLRequestMapperFactory);\r
-    conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);\r
-}\r
-\r
-short Override::acceptNode(const DOMNode* node) const\r
-{\r
-    if (!XMLString::equals(node->getNamespaceURI(),shibspconstants::SHIB1SPCONFIG_NS))\r
-        return FILTER_ACCEPT;\r
-    const XMLCh* name=node->getLocalName();\r
-    if (XMLString::equals(name,Host) ||\r
-        XMLString::equals(name,Path) ||\r
-        XMLString::equals(name,_AccessControl) ||\r
-        XMLString::equals(name,htaccess) ||\r
-        XMLString::equals(name,AccessControlProvider))\r
-        return FILTER_REJECT;\r
-\r
-    return FILTER_ACCEPT;\r
-}\r
-\r
-void Override::loadACL(const DOMElement* e, Category& log)\r
-{\r
-    try {\r
-        const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);\r
-        if (acl) {\r
-            log.info("building Apache htaccess AccessControl provider...");\r
-            m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl);\r
-        }\r
-        else {\r
-            acl=XMLHelper::getFirstChildElement(e,_AccessControl);\r
-            if (acl) {\r
-                log.info("building XML-based AccessControl provider...");\r
-                m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl);\r
-            }\r
-            else {\r
-                acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);\r
-                if (acl) {\r
-                    xmltooling::auto_ptr_char type(acl->getAttributeNS(NULL,type));\r
-                    log.info("building AccessControl provider of type %s...",type.get());\r
-                    m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(type.get(),acl);\r
-                }\r
-            }\r
-        }\r
-    }\r
-    catch (exception& ex) {\r
-        log.crit("exception building AccessControl provider: %s", ex.what());\r
-        m_acl = new AccessControlDummy();\r
-    }\r
-}\r
-\r
-Override::Override(const DOMElement* e, Category& log, const Override* base) : m_base(base), m_acl(NULL)\r
-{\r
-    try {\r
-        // Load the property set.\r
-        load(e,log,this);\r
-        \r
-        // Load any AccessControl provider.\r
-        loadACL(e,log);\r
-    \r
-        // Handle nested Paths.\r
-        DOMElement* path = XMLHelper::getFirstChildElement(e,Path);\r
-        for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {\r
-            const XMLCh* n=path->getAttributeNS(NULL,name);\r
-            \r
-            // Skip any leading slashes.\r
-            while (n && *n==chForwardSlash)\r
-                n++;\r
-            \r
-            // Check for empty name.\r
-            if (!n || !*n) {\r
-                log.warn("skipping Path element (%d) with empty name attribute", i);\r
-                continue;\r
-            }\r
-\r
-            // Check for an embedded slash.\r
-            int slash=XMLString::indexOf(n,chForwardSlash);\r
-            if (slash>0) {\r
-                // Copy the first path segment.\r
-                XMLCh* namebuf=new XMLCh[slash + 1];\r
-                for (int pos=0; pos < slash; pos++)\r
-                    namebuf[pos]=n[pos];\r
-                namebuf[slash]=chNull;\r
-                \r
-                // Move past the slash in the original pathname.\r
-                n=n+slash+1;\r
-                \r
-                // Skip any leading slashes again.\r
-                while (*n==chForwardSlash)\r
-                    n++;\r
-                \r
-                if (*n) {\r
-                    // Create a placeholder Path element for the first path segment and replant under it.\r
-                    DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibspconstants::SHIB1SPCONFIG_NS,Path);\r
-                    newpath->setAttributeNS(NULL,name,namebuf);\r
-                    path->setAttributeNS(NULL,name,n);\r
-                    path->getParentNode()->replaceChild(newpath,path);\r
-                    newpath->appendChild(path);\r
-                    \r
-                    // Repoint our locals at the new parent.\r
-                    path=newpath;\r
-                    n=path->getAttributeNS(NULL,name);\r
-                }\r
-                else {\r
-                    // All we had was a pathname with trailing slash(es), so just reset it without them.\r
-                    path->setAttributeNS(NULL,name,namebuf);\r
-                    n=path->getAttributeNS(NULL,name);\r
-                }\r
-                delete[] namebuf;\r
-            }\r
-            \r
-            Override* o=new Override(path,log,this);\r
-            pair<bool,const char*> name=o->getString("name");\r
-            char* dup=strdup(name.second);\r
-            for (char* pch=dup; *pch; pch++)\r
-                *pch=tolower(*pch);\r
-            if (m_map.count(dup)) {\r
-                log.warn("Skipping duplicate Path element (%s)",dup);\r
-                free(dup);\r
-                delete o;\r
-                continue;\r
-            }\r
-            m_map[dup]=o;\r
-            free(dup);\r
-        }\r
-    }\r
-    catch (exception&) {\r
-        delete m_acl;\r
-        for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());\r
-        throw;\r
-    }\r
-}\r
-\r
-Override::~Override()\r
-{\r
-    delete m_acl;\r
-    for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());\r
-}\r
-\r
-pair<bool,bool> Override::getBool(const char* name, const char* ns) const\r
-{\r
-    pair<bool,bool> ret=DOMPropertySet::getBool(name,ns);\r
-    if (ret.first)\r
-        return ret;\r
-    return m_base ? m_base->getBool(name,ns) : ret;\r
-}\r
-\r
-pair<bool,const char*> Override::getString(const char* name, const char* ns) const\r
-{\r
-    pair<bool,const char*> ret=DOMPropertySet::getString(name,ns);\r
-    if (ret.first)\r
-        return ret;\r
-    return m_base ? m_base->getString(name,ns) : ret;\r
-}\r
-\r
-pair<bool,const XMLCh*> Override::getXMLString(const char* name, const char* ns) const\r
-{\r
-    pair<bool,const XMLCh*> ret=DOMPropertySet::getXMLString(name,ns);\r
-    if (ret.first)\r
-        return ret;\r
-    return m_base ? m_base->getXMLString(name,ns) : ret;\r
-}\r
-\r
-pair<bool,unsigned int> Override::getUnsignedInt(const char* name, const char* ns) const\r
-{\r
-    pair<bool,unsigned int> ret=DOMPropertySet::getUnsignedInt(name,ns);\r
-    if (ret.first)\r
-        return ret;\r
-    return m_base ? m_base->getUnsignedInt(name,ns) : ret;\r
-}\r
-\r
-pair<bool,int> Override::getInt(const char* name, const char* ns) const\r
-{\r
-    pair<bool,int> ret=DOMPropertySet::getInt(name,ns);\r
-    if (ret.first)\r
-        return ret;\r
-    return m_base ? m_base->getInt(name,ns) : ret;\r
-}\r
-\r
-const PropertySet* Override::getPropertySet(const char* name, const char* ns) const\r
-{\r
-    const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns);\r
-    if (ret || !m_base)\r
-        return ret;\r
-    return m_base->getPropertySet(name,ns);\r
-}\r
-\r
-const Override* Override::locate(const char* path) const\r
-{\r
-    char* dup=strdup(path);\r
-    char* sep=strchr(dup,'?');\r
-    if (sep)\r
-        *sep=0;\r
-    for (char* pch=dup; *pch; pch++)\r
-        *pch=tolower(*pch);\r
-        \r
-    const Override* o=this;\r
-    \r
-#ifdef HAVE_STRTOK_R\r
-    char* pos=NULL;\r
-    const char* token=strtok_r(dup,"/",&pos);\r
-#else\r
-    const char* token=strtok(dup,"/");\r
-#endif\r
-    while (token)\r
-    {\r
-        map<string,Override*>::const_iterator i=o->m_map.find(token);\r
-        if (i==o->m_map.end())\r
-            break;\r
-        o=i->second;\r
-#ifdef HAVE_STRTOK_R\r
-        token=strtok_r(NULL,"/",&pos);\r
-#else\r
-        token=strtok(NULL,"/");\r
-#endif\r
-    }\r
-\r
-    free(dup);\r
-    return o;\r
-}\r
-\r
-XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)\r
-{\r
-#ifdef _DEBUG\r
-    xmltooling::NDC ndc("XMLRequestMapperImpl");\r
-#endif\r
-\r
-    // Load the property set.\r
-    load(e,log,this);\r
-    \r
-    // Load any AccessControl provider.\r
-    loadACL(e,log);\r
-\r
-    // Loop over the Host elements.\r
-    const DOMElement* host = XMLHelper::getFirstChildElement(e,Host);\r
-    for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {\r
-        const XMLCh* n=host->getAttributeNS(NULL,name);\r
-        if (!n || !*n) {\r
-            log.warn("Skipping Host element (%d) with empty name attribute",i);\r
-            continue;\r
-        }\r
-        \r
-        Override* o=new Override(host,log,this);\r
-        pair<bool,const char*> name=o->getString("name");\r
-        pair<bool,const char*> scheme=o->getString("scheme");\r
-        pair<bool,const char*> port=o->getString("port");\r
-        \r
-        char* dup=strdup(name.second);\r
-        for (char* pch=dup; *pch; pch++)\r
-            *pch=tolower(*pch);\r
-        auto_ptr<char> dupwrap(dup);\r
-\r
-        if (!scheme.first && port.first) {\r
-            // No scheme, but a port, so assume http.\r
-            scheme = pair<bool,const char*>(true,"http");\r
-        }\r
-        else if (scheme.first && !port.first) {\r
-            // Scheme, no port, so default it.\r
-            // XXX Use getservbyname instead?\r
-            port.first = true;\r
-            if (!strcmp(scheme.second,"http"))\r
-                port.second = "80";\r
-            else if (!strcmp(scheme.second,"https"))\r
-                port.second = "443";\r
-            else if (!strcmp(scheme.second,"ftp"))\r
-                port.second = "21";\r
-            else if (!strcmp(scheme.second,"ldap"))\r
-                port.second = "389";\r
-            else if (!strcmp(scheme.second,"ldaps"))\r
-                port.second = "636";\r
-        }\r
-\r
-        if (scheme.first) {\r
-            string url(scheme.second);\r
-            url=url + "://" + dup;\r
-            \r
-            // Is this the default port?\r
-            if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||\r
-                (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||\r
-                (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||\r
-                (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||\r
-                (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {\r
-                // First store a port-less version.\r
-                if (m_map.count(url) || m_extras.count(url)) {\r
-                    log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                    delete o;\r
-                    continue;\r
-                }\r
-                m_map[url]=o;\r
-                log.debug("Added <Host> mapping for %s",url.c_str());\r
-                \r
-                // Now append the port. We use the extras vector, to avoid double freeing the object later.\r
-                url=url + ':' + port.second;\r
-                m_extras[url]=o;\r
-                log.debug("Added <Host> mapping for %s",url.c_str());\r
-            }\r
-            else {\r
-                url=url + ':' + port.second;\r
-                if (m_map.count(url) || m_extras.count(url)) {\r
-                    log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                    delete o;\r
-                    continue;\r
-                }\r
-                m_map[url]=o;\r
-                log.debug("Added <Host> mapping for %s",url.c_str());\r
-            }\r
-        }\r
-        else {\r
-            // No scheme or port, so we enter dual hosts on http:80 and https:443\r
-            string url("http://");\r
-            url = url + dup;\r
-            if (m_map.count(url) || m_extras.count(url)) {\r
-                log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                delete o;\r
-                continue;\r
-            }\r
-            m_map[url]=o;\r
-            log.debug("Added <Host> mapping for %s",url.c_str());\r
-            \r
-            url = url + ":80";\r
-            if (m_map.count(url) || m_extras.count(url)) {\r
-                log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                continue;\r
-            }\r
-            m_extras[url]=o;\r
-            log.debug("Added <Host> mapping for %s",url.c_str());\r
-            \r
-            url = "https://";\r
-            url = url + dup;\r
-            if (m_map.count(url) || m_extras.count(url)) {\r
-                log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                continue;\r
-            }\r
-            m_extras[url]=o;\r
-            log.debug("Added <Host> mapping for %s",url.c_str());\r
-            \r
-            url = url + ":443";\r
-            if (m_map.count(url) || m_extras.count(url)) {\r
-                log.warn("Skipping duplicate Host element (%s)",url.c_str());\r
-                continue;\r
-            }\r
-            m_extras[url]=o;\r
-            log.debug("Added <Host> mapping for %s",url.c_str());\r
-        }\r
-    }\r
-}\r
-\r
-const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const\r
-{\r
-    const Override* o=NULL;\r
-    map<string,Override*>::const_iterator i=m_map.find(vhost);\r
-    if (i!=m_map.end())\r
-        o=i->second;\r
-    else {\r
-        i=m_extras.find(vhost);\r
-        if (i!=m_extras.end())\r
-            o=i->second;\r
-    }\r
-    \r
-    return o ? o->locate(path) : this;\r
-}\r
-\r
-pair<bool,DOMElement*> XMLRequestMapper::load()\r
-{\r
-    // Load from source using base class.\r
-    pair<bool,DOMElement*> raw = ReloadableXMLFile::load();\r
-    \r
-    // If we own it, wrap it.\r
-    XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);\r
-\r
-    XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);\r
-    \r
-    // If we held the document, transfer it to the impl. If we didn't, it's a no-op.\r
-    impl->setDocument(docjanitor.release());\r
-\r
-    delete m_impl;\r
-    m_impl = impl;\r
-\r
-    return make_pair(false,(DOMElement*)NULL);\r
-}\r
-\r
-RequestMapper::Settings XMLRequestMapper::getSettings(const SPRequest& request) const\r
-{\r
-    ostringstream vhost;\r
-    vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();\r
-\r
-    const Override* o=m_impl->findOverride(vhost.str().c_str(), request.getRequestURI());\r
-\r
-    if (m_log.isDebugEnabled()) {\r
-#ifdef _DEBUG\r
-        xmltooling::NDC ndc("getSettings");\r
-#endif\r
-        pair<bool,const char*> ret=o->getString("applicationId");\r
-        m_log.debug("mapped %s%s to %s", vhost.str().c_str(), request.getRequestURI() ? request.getRequestURI() : "", ret.second);\r
-    }\r
-\r
-    return Settings(o,o->getAC());\r
-}\r
+/*
+ *  Copyright 2001-2007 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** XMLRequestMapper.cpp
+ * 
+ * XML-based RequestMapper implementation
+ */
+
+#include "internal.h"
+#include "exceptions.h"
+#include "AccessControl.h"
+#include "RequestMapper.h"
+#include "SPRequest.h"
+#include "util/DOMPropertySet.h"
+#include "util/SPConstants.h"
+
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/ReloadableXMLFile.h>
+#include <xmltooling/util/XMLHelper.h>
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xercesc/util/regx/RegularExpression.hpp>
+
+using namespace shibsp;
+using namespace xmltooling;
+using namespace std;
+
+namespace shibsp {
+
+    // Blocks access when an ACL plugin fails to load. 
+    class AccessControlDummy : public AccessControl
+    {
+    public:
+        Lockable* lock() {
+            return this;
+        }
+        
+        void unlock() {}
+    
+        aclresult_t authorized(const SPRequest& request, const Session* session) const {
+            return shib_acl_false;
+        }
+    };
+
+    class Override : public DOMPropertySet, public DOMNodeFilter
+    {
+    public:
+        Override() : m_acl(NULL) {}
+        Override(const DOMElement* e, Category& log, const Override* base=NULL);
+        ~Override();
+
+        // Provides filter to exclude special config elements.
+        short acceptNode(const DOMNode* node) const {
+            return FILTER_REJECT;
+        }
+
+        const Override* locate(const HTTPRequest& request) const;
+        AccessControl* getAC() const { return (m_acl ? m_acl : (getParent() ? dynamic_cast<const Override*>(getParent())->getAC() : NULL)); }
+        
+    protected:
+        void loadACL(const DOMElement* e, Category& log);
+        
+        map<string,Override*> m_map;
+        vector< pair<RegularExpression*,Override*> > m_regexps;
+        vector< pair< pair<string,RegularExpression*>,Override*> > m_queries;
+    
+    private:
+        AccessControl* m_acl;
+    };
+
+    class XMLRequestMapperImpl : public Override
+    {
+    public:
+        XMLRequestMapperImpl(const DOMElement* e, Category& log);
+
+        ~XMLRequestMapperImpl() {
+            if (m_document)
+                m_document->release();
+        }
+
+        void setDocument(DOMDocument* doc) {
+            m_document = doc;
+        }
+    
+        const Override* findOverride(const char* vhost, const HTTPRequest& request) const;
+
+    private:    
+        map<string,Override*> m_extras;
+        DOMDocument* m_document;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( push )
+    #pragma warning( disable : 4250 )
+#endif
+
+    class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile
+    {
+    public:
+        XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e,Category::getInstance(SHIBSP_LOGCAT".RequestMapper")), m_impl(NULL) {
+            load();
+        }
+
+        ~XMLRequestMapper() {
+            delete m_impl;
+        }
+
+        Settings getSettings(const HTTPRequest& request) const;
+
+    protected:
+        pair<bool,DOMElement*> load();
+
+    private:
+        XMLRequestMapperImpl* m_impl;
+    };
+
+#if defined (_MSC_VER)
+    #pragma warning( pop )
+#endif
+
+    RequestMapper* SHIBSP_DLLLOCAL XMLRequestMapperFactory(const DOMElement* const & e)
+    {
+        return new XMLRequestMapper(e);
+    }
+
+    static const XMLCh _AccessControl[] =           UNICODE_LITERAL_13(A,c,c,e,s,s,C,o,n,t,r,o,l);
+    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);
+    static const XMLCh Host[] =                     UNICODE_LITERAL_4(H,o,s,t);
+    static const XMLCh HostRegex[] =                UNICODE_LITERAL_9(H,o,s,t,R,e,g,e,x);
+    static const XMLCh htaccess[] =                 UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);
+    static const XMLCh ignoreCase[] =               UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);
+    static const XMLCh ignoreOption[] =             UNICODE_LITERAL_1(i);
+    static const XMLCh Path[] =                     UNICODE_LITERAL_4(P,a,t,h);
+    static const XMLCh PathRegex[] =                UNICODE_LITERAL_9(P,a,t,h,R,e,g,e,x);
+    static const XMLCh Query[] =                    UNICODE_LITERAL_5(Q,u,e,r,y);
+    static const XMLCh name[] =                     UNICODE_LITERAL_4(n,a,m,e);
+    static const XMLCh regex[] =                    UNICODE_LITERAL_5(r,e,g,e,x);
+    static const XMLCh _type[] =                    UNICODE_LITERAL_4(t,y,p,e);
+}
+
+void SHIBSP_API shibsp::registerRequestMappers()
+{
+    SPConfig& conf=SPConfig::getConfig();
+    conf.RequestMapperManager.registerFactory(XML_REQUEST_MAPPER, XMLRequestMapperFactory);
+    conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
+}
+
+void Override::loadACL(const DOMElement* e, Category& log)
+{
+    try {
+        const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
+        if (acl) {
+            log.info("building Apache htaccess AccessControl provider...");
+            m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl);
+        }
+        else {
+            acl=XMLHelper::getFirstChildElement(e,_AccessControl);
+            if (acl) {
+                log.info("building XML-based AccessControl provider...");
+                m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl);
+            }
+            else {
+                acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
+                if (acl) {
+                    auto_ptr_char type(acl->getAttributeNS(NULL,_type));
+                    log.info("building AccessControl provider of type %s...",type.get());
+                    m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(type.get(),acl);
+                }
+            }
+        }
+    }
+    catch (exception& ex) {
+        log.crit("exception building AccessControl provider: %s", ex.what());
+        m_acl = new AccessControlDummy();
+    }
+}
+
+Override::Override(const DOMElement* e, Category& log, const Override* base) : m_acl(NULL)
+{
+    try {
+        // Load the property set.
+        load(e,NULL,this);
+        setParent(base);
+        
+        // Load any AccessControl provider.
+        loadACL(e,log);
+    
+        // Handle nested Paths.
+        DOMElement* path = XMLHelper::getFirstChildElement(e,Path);
+        for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Path)) {
+            const XMLCh* n=path->getAttributeNS(NULL,name);
+            
+            // Skip any leading slashes.
+            while (n && *n==chForwardSlash)
+                n++;
+            
+            // Check for empty name.
+            if (!n || !*n) {
+                log.warn("skipping Path element (%d) with empty name attribute", i);
+                continue;
+            }
+
+            // Check for an embedded slash.
+            int slash=XMLString::indexOf(n,chForwardSlash);
+            if (slash>0) {
+                // Copy the first path segment.
+                XMLCh* namebuf=new XMLCh[slash + 1];
+                for (int pos=0; pos < slash; pos++)
+                    namebuf[pos]=n[pos];
+                namebuf[slash]=chNull;
+                
+                // Move past the slash in the original pathname.
+                n=n+slash+1;
+                
+                // Skip any leading slashes again.
+                while (*n==chForwardSlash)
+                    n++;
+                
+                if (*n) {
+                    // Create a placeholder Path element for the first path segment and replant under it.
+                    DOMElement* newpath=path->getOwnerDocument()->createElementNS(shibspconstants::SHIB2SPCONFIG_NS,Path);
+                    newpath->setAttributeNS(NULL,name,namebuf);
+                    path->setAttributeNS(NULL,name,n);
+                    path->getParentNode()->replaceChild(newpath,path);
+                    newpath->appendChild(path);
+                    
+                    // Repoint our locals at the new parent.
+                    path=newpath;
+                    n=path->getAttributeNS(NULL,name);
+                }
+                else {
+                    // All we had was a pathname with trailing slash(es), so just reset it without them.
+                    path->setAttributeNS(NULL,name,namebuf);
+                    n=path->getAttributeNS(NULL,name);
+                }
+                delete[] namebuf;
+            }
+            
+            Override* o=new Override(path,log,this);
+            pair<bool,const char*> name=o->getString("name");
+            char* dup=strdup(name.second);
+            for (char* pch=dup; *pch; pch++)
+                *pch=tolower(*pch);
+            if (m_map.count(dup)) {
+                log.warn("skipping duplicate Path element (%s)",dup);
+                free(dup);
+                delete o;
+                continue;
+            }
+            m_map[dup]=o;
+            log.debug("added Path mapping (%s)", dup);
+            free(dup);
+        }
+
+        if (!XMLString::equals(e->getLocalName(), PathRegex)) {
+            // Handle nested PathRegexs.
+            path = XMLHelper::getFirstChildElement(e,PathRegex);
+            for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,PathRegex)) {
+                const XMLCh* n=path->getAttributeNS(NULL,regex);
+                if (!n || !*n) {
+                    log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
+                    continue;
+                }
+
+                auto_ptr<Override> o(new Override(path,log,this));
+
+                const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);
+                try {
+                    auto_ptr<RegularExpression> re(
+                        new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
+                        );
+                    m_regexps.push_back(make_pair(re.release(), o.release()));
+                }
+                catch (XMLException& ex) {
+                    auto_ptr_char tmp(ex.getMessage());
+                    log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
+                    throw ConfigurationException("Invalid regular expression in PathRegex element.");
+                }
+
+                if (log.isDebugEnabled())
+                    log.debug("added <PathRegex> mapping (%s)", m_regexps.back().second->getString("regex").second);
+            }
+        }
+
+        // Handle nested Querys.
+        path = XMLHelper::getFirstChildElement(e,Query);
+        for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Query)) {
+            const XMLCh* n=path->getAttributeNS(NULL,name);
+            if (!n || !*n) {
+                log.warn("skipping Query element (%d) with empty name attribute",i);
+                continue;
+            }
+            auto_ptr_char ntemp(n);
+            const XMLCh* v=path->getAttributeNS(NULL,regex);
+
+            auto_ptr<Override> o(new Override(path,log,this));
+            try {
+                RegularExpression* re = NULL;
+                if (v && *v)
+                    re = new RegularExpression(v);
+                m_queries.push_back(make_pair(make_pair(string(ntemp.get()),re), o.release()));
+            }
+            catch (XMLException& ex) {
+                auto_ptr_char tmp(ex.getMessage());
+                log.error("caught exception while parsing Query regular expression (%d): %s", i, tmp.get());
+                throw ConfigurationException("Invalid regular expression in Query element.");
+            }
+            
+            log.debug("added <Query> mapping (%s)", ntemp.get());
+        }
+    }
+    catch (exception&) {
+        delete m_acl;
+        for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
+        for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
+            delete i->first;
+            delete i->second;
+        }
+        for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
+            delete j->first.second;
+            delete j->second;
+        }
+        throw;
+    }
+}
+
+Override::~Override()
+{
+    delete m_acl;
+    for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());
+    for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {
+        delete i->first;
+        delete i->second;
+    }
+    for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
+        delete j->first.second;
+        delete j->second;
+    }
+}
+
+const Override* Override::locate(const HTTPRequest& request) const
+{
+    // This function is confusing because it's *not* recursive.
+    // The whole path is tokenized and mapped in a loop, so the
+    // path parameter starts with the entire request path and
+    // we can skip the leading slash as irrelevant.
+    const char* path = request.getRequestURI();
+    if (*path == '/')
+        path++;
+
+    // Now we copy the path, chop the query string, and lower case it.
+    char* dup=strdup(path);
+    char* sep=strchr(dup,'?');
+    if (sep)
+        *sep=0;
+    for (char* pch=dup; *pch; pch++)
+        *pch=tolower(*pch);
+
+    // Default is for the current object to provide settings.
+    const Override* o=this;
+
+    // Tokenize the path by segment and try and map each segment.
+#ifdef HAVE_STRTOK_R
+    char* pos=NULL;
+    const char* token=strtok_r(dup,"/",&pos);
+#else
+    const char* token=strtok(dup,"/");
+#endif
+    while (token) {
+        map<string,Override*>::const_iterator i=o->m_map.find(token);
+        if (i==o->m_map.end())
+            break;  // Once there's no match, we've consumed as much of the path as possible here.
+        // We found a match, so reset the settings pointer.
+        o=i->second;
+        
+        // We descended a step down the path, so we need to advance the original
+        // parameter for the regex step later.
+        path += strlen(token);
+        if (*path == '/')
+            path++;
+
+        // Get the next segment, if any.
+#ifdef HAVE_STRTOK_R
+        token=strtok_r(NULL,"/",&pos);
+#else
+        token=strtok(NULL,"/");
+#endif
+    }
+
+    free(dup);
+
+    // If there's anything left, we try for a regex match on the rest of the path minus the query string.
+    if (*path) {
+        string path2(path);
+        path2 = path2.substr(0,path2.find('?'));
+
+        for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
+            if (re->first->matches(path2.c_str())) {
+                o = re->second;
+                break;
+            }
+        }
+    }
+
+    // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop.
+    bool descended;
+    do {
+        descended = false;
+        for (vector< pair< pair<string,RegularExpression*>,Override*> >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) {
+            vector<const char*> vals;
+            if (request.getParameters(q->first.first.c_str(), vals)) {
+                if (q->first.second) {
+                    // We have to match one of the values.
+                    for (vector<const char*>::const_iterator v = vals.begin(); v != vals.end(); ++v) {
+                        if (q->first.second->matches(*v)) {
+                            o = q->second;
+                            descended = true;
+                            break;
+                        }
+                    }
+                }
+                else {
+                    // The simple presence of the parameter is sufficient to match.
+                    o = q->second;
+                    descended = true;
+                }
+            }
+        }
+    } while (descended);
+
+    return o;
+}
+
+XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
+{
+#ifdef _DEBUG
+    xmltooling::NDC ndc("XMLRequestMapperImpl");
+#endif
+
+    // Load the property set.
+    load(e,NULL,this);
+    
+    // Load any AccessControl provider.
+    loadACL(e,log);
+
+    // Loop over the HostRegex elements.
+    const DOMElement* host = XMLHelper::getFirstChildElement(e,HostRegex);
+    for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,HostRegex)) {
+        const XMLCh* n=host->getAttributeNS(NULL,regex);
+        if (!n || !*n) {
+            log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);
+            continue;
+        }
+
+        auto_ptr<Override> o(new Override(host,log,this));
+
+        const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);
+        try {
+            auto_ptr<RegularExpression> re(
+                new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
+                );
+            m_regexps.push_back(make_pair(re.release(), o.release()));
+        }
+        catch (XMLException& ex) {
+            auto_ptr_char tmp(ex.getMessage());
+            log.error("caught exception while parsing HostRegex regular expression (%d): %s", i, tmp.get());
+        }
+
+        log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);
+    }
+
+    // Loop over the Host elements.
+    host = XMLHelper::getFirstChildElement(e,Host);
+    for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,Host)) {
+        const XMLCh* n=host->getAttributeNS(NULL,name);
+        if (!n || !*n) {
+            log.warn("Skipping Host element (%d) with empty name attribute",i);
+            continue;
+        }
+        
+        Override* o=new Override(host,log,this);
+        pair<bool,const char*> name=o->getString("name");
+        pair<bool,const char*> scheme=o->getString("scheme");
+        pair<bool,const char*> port=o->getString("port");
+        
+        char* dup=strdup(name.second);
+        for (char* pch=dup; *pch; pch++)
+            *pch=tolower(*pch);
+        auto_ptr<char> dupwrap(dup);
+
+        if (!scheme.first && port.first) {
+            // No scheme, but a port, so assume http.
+            scheme = pair<bool,const char*>(true,"http");
+        }
+        else if (scheme.first && !port.first) {
+            // Scheme, no port, so default it.
+            // XXX Use getservbyname instead?
+            port.first = true;
+            if (!strcmp(scheme.second,"http"))
+                port.second = "80";
+            else if (!strcmp(scheme.second,"https"))
+                port.second = "443";
+            else if (!strcmp(scheme.second,"ftp"))
+                port.second = "21";
+            else if (!strcmp(scheme.second,"ldap"))
+                port.second = "389";
+            else if (!strcmp(scheme.second,"ldaps"))
+                port.second = "636";
+        }
+
+        if (scheme.first) {
+            string url(scheme.second);
+            url=url + "://" + dup;
+            
+            // Is this the default port?
+            if ((!strcmp(scheme.second,"http") && !strcmp(port.second,"80")) ||
+                (!strcmp(scheme.second,"https") && !strcmp(port.second,"443")) ||
+                (!strcmp(scheme.second,"ftp") && !strcmp(port.second,"21")) ||
+                (!strcmp(scheme.second,"ldap") && !strcmp(port.second,"389")) ||
+                (!strcmp(scheme.second,"ldaps") && !strcmp(port.second,"636"))) {
+                // First store a port-less version.
+                if (m_map.count(url) || m_extras.count(url)) {
+                    log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                    delete o;
+                    continue;
+                }
+                m_map[url]=o;
+                log.debug("Added <Host> mapping for %s",url.c_str());
+                
+                // Now append the port. We use the extras vector, to avoid double freeing the object later.
+                url=url + ':' + port.second;
+                m_extras[url]=o;
+                log.debug("Added <Host> mapping for %s",url.c_str());
+            }
+            else {
+                url=url + ':' + port.second;
+                if (m_map.count(url) || m_extras.count(url)) {
+                    log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                    delete o;
+                    continue;
+                }
+                m_map[url]=o;
+                log.debug("Added <Host> mapping for %s",url.c_str());
+            }
+        }
+        else {
+            // No scheme or port, so we enter dual hosts on http:80 and https:443
+            string url("http://");
+            url = url + dup;
+            if (m_map.count(url) || m_extras.count(url)) {
+                log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                delete o;
+                continue;
+            }
+            m_map[url]=o;
+            log.debug("Added <Host> mapping for %s",url.c_str());
+            
+            url = url + ":80";
+            if (m_map.count(url) || m_extras.count(url)) {
+                log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                continue;
+            }
+            m_extras[url]=o;
+            log.debug("Added <Host> mapping for %s",url.c_str());
+            
+            url = "https://";
+            url = url + dup;
+            if (m_map.count(url) || m_extras.count(url)) {
+                log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                continue;
+            }
+            m_extras[url]=o;
+            log.debug("Added <Host> mapping for %s",url.c_str());
+            
+            url = url + ":443";
+            if (m_map.count(url) || m_extras.count(url)) {
+                log.warn("Skipping duplicate Host element (%s)",url.c_str());
+                continue;
+            }
+            m_extras[url]=o;
+            log.debug("Added <Host> mapping for %s",url.c_str());
+        }
+    }
+}
+
+const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const
+{
+    const Override* o=NULL;
+    map<string,Override*>::const_iterator i=m_map.find(vhost);
+    if (i!=m_map.end())
+        o=i->second;
+    else {
+        i=m_extras.find(vhost);
+        if (i!=m_extras.end())
+            o=i->second;
+        else {
+            for (vector< pair<RegularExpression*,Override*> >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) {
+                if (re->first->matches(vhost))
+                    o=re->second;
+            }
+        }
+    }
+    
+    return o ? o->locate(request) : this;
+}
+
+pair<bool,DOMElement*> XMLRequestMapper::load()
+{
+    // Load from source using base class.
+    pair<bool,DOMElement*> raw = ReloadableXMLFile::load();
+    
+    // If we own it, wrap it.
+    XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : NULL);
+
+    XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second,m_log);
+    
+    // If we held the document, transfer it to the impl. If we didn't, it's a no-op.
+    impl->setDocument(docjanitor.release());
+
+    delete m_impl;
+    m_impl = impl;
+
+    return make_pair(false,(DOMElement*)NULL);
+}
+
+RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const
+{
+    ostringstream vhost;
+    vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();
+
+    const Override* o=m_impl->findOverride(vhost.str().c_str(), request);
+
+    if (m_log.isDebugEnabled()) {
+#ifdef _DEBUG
+        xmltooling::NDC ndc("getSettings");
+#endif
+        pair<bool,const char*> ret=o->getString("applicationId");
+        m_log.debug("mapped %s%s to %s", vhost.str().c_str(), request.getRequestURI() ? request.getRequestURI() : "", ret.second);
+    }
+
+    return Settings(o,o->getAC());
+}