Convert logging to log4shib via compile time switch.
[shibboleth/cpp-sp.git] / shibsp / impl / XMLRequestMapper.cpp
index 14d0a5d..9119623 100644 (file)
@@ -20,6 +20,7 @@
  */\r
 \r
 #include "internal.h"\r
+#include "exceptions.h"\r
 #include "AccessControl.h"\r
 #include "RequestMapper.h"\r
 #include "SPRequest.h"\r
 #include <xmltooling/util/ReloadableXMLFile.h>\r
 #include <xmltooling/util/XMLHelper.h>\r
 #include <xercesc/util/XMLUniDefs.hpp>\r
+#include <xercesc/util/regx/RegularExpression.hpp>\r
 \r
 using namespace shibsp;\r
 using namespace xmltooling;\r
-using namespace log4cpp;\r
 using namespace std;\r
 \r
 namespace shibsp {\r
@@ -66,7 +67,7 @@ namespace shibsp {
         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
+        const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:2.0:native:sp:config") const;\r
         \r
         // Provides filter to exclude special config elements.\r
         short acceptNode(const DOMNode* node) const;\r
@@ -78,6 +79,7 @@ namespace shibsp {
         void loadACL(const DOMElement* e, Category& log);\r
         \r
         map<string,Override*> m_map;\r
+        vector< pair<RegularExpression*,Override*> > m_regexps;\r
     \r
     private:\r
         const Override* m_base;\r
@@ -139,13 +141,18 @@ namespace shibsp {
         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 _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 HostRegex[] =                UNICODE_LITERAL_9(H,o,s,t,R,e,g,e,x);\r
+    static const XMLCh htaccess[] =                 UNICODE_LITERAL_8(h,t,a,c,c,e,s,s);\r
+    static const XMLCh ignoreCase[] =               UNICODE_LITERAL_10(i,g,n,o,r,e,C,a,s,e);\r
+    static const XMLCh ignoreOption[] =             UNICODE_LITERAL_1(i);\r
     static const XMLCh Path[] =                     UNICODE_LITERAL_4(P,a,t,h);\r
+    static const XMLCh PathRegex[] =                UNICODE_LITERAL_9(P,a,t,h,R,e,g,e,x);\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
+    static const XMLCh regex[] =                    UNICODE_LITERAL_5(r,e,g,e,x);\r
+    static const XMLCh _type[] =                    UNICODE_LITERAL_4(t,y,p,e);\r
 }\r
 \r
 void SHIBSP_API shibsp::registerRequestMappers()\r
@@ -190,7 +197,7 @@ void Override::loadACL(const DOMElement* e, Category& log)
             else {\r
                 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);\r
                 if (acl) {\r
-                    xmltooling::auto_ptr_char type(acl->getAttributeNS(NULL,type));\r
+                    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
@@ -275,8 +282,38 @@ Override::Override(const DOMElement* e, Category& log, const Override* base) : m
                 continue;\r
             }\r
             m_map[dup]=o;\r
+            log.debug("Added Path mapping (%s)", dup);\r
             free(dup);\r
         }\r
+\r
+        if (!XMLString::equals(e->getLocalName(), PathRegex)) {\r
+            // Handle nested PathRegexs.\r
+            path = XMLHelper::getFirstChildElement(e,PathRegex);\r
+            for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,PathRegex)) {\r
+                const XMLCh* n=path->getAttributeNS(NULL,regex);\r
+                if (!n || !*n) {\r
+                    log.warn("Skipping PathRegex element (%d) with empty regex attribute",i);\r
+                    continue;\r
+                }\r
+\r
+                auto_ptr<Override> o(new Override(path,log,this));\r
+\r
+                const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);\r
+                try {\r
+                    auto_ptr<RegularExpression> re(\r
+                        new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)\r
+                        );\r
+                    m_regexps.push_back(make_pair(re.release(), o.release()));\r
+                }\r
+                catch (XMLException& ex) {\r
+                    auto_ptr_char tmp(ex.getMessage());\r
+                    log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());\r
+                    throw ConfigurationException("Invalid regular expression in PathRegex element.");\r
+                }\r
+\r
+                log.debug("Added <PathRegex> mapping (%s)", m_regexps.back().second->getString("regex").second);\r
+            }\r
+        }\r
     }\r
     catch (exception&) {\r
         delete m_acl;\r
@@ -289,6 +326,10 @@ Override::~Override()
 {\r
     delete m_acl;\r
     for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair<string,Override>());\r
+    for (vector< pair<RegularExpression*,Override*> >::iterator i = m_regexps.begin(); i != m_regexps.end(); ++i) {\r
+        delete i->first;\r
+        delete i->second;\r
+    }\r
 }\r
 \r
 pair<bool,bool> Override::getBool(const char* name, const char* ns) const\r
@@ -341,27 +382,45 @@ const PropertySet* Override::getPropertySet(const char* name, const char* ns) co
 \r
 const Override* Override::locate(const char* path) const\r
 {\r
+    // This function is confusing because it's *not* recursive.\r
+    // The whole path is tokenized and mapped in a loop, so the\r
+    // path parameter starts with the entire request path and\r
+    // we can skip the leading slash as irrelevant.\r
+    if (*path == '/')\r
+        path++;\r
+\r
+    // Now we copy the path, chop the query string, and lower case it.\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
+\r
+    // Default is for the current object to provide settings.\r
     const Override* o=this;\r
-    \r
+\r
+    // Tokenize the path by segment and try and map each segment.\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
+    while (token) {\r
         map<string,Override*>::const_iterator i=o->m_map.find(token);\r
         if (i==o->m_map.end())\r
-            break;\r
+            break;  // Once there's no match, we've consumed as much of the path as possible here.\r
+        // We found a match, so reset the settings pointer.\r
         o=i->second;\r
+        \r
+        // We descended a step down the path, so we need to advance the original\r
+        // parameter for the regex step later.\r
+        path += strlen(token);\r
+        if (*path == '/')\r
+            path++;\r
+\r
+        // Get the next segment, if any.\r
 #ifdef HAVE_STRTOK_R\r
         token=strtok_r(NULL,"/",&pos);\r
 #else\r
@@ -370,6 +429,15 @@ const Override* Override::locate(const char* path) const
     }\r
 \r
     free(dup);\r
+\r
+    // If there's anything left, we try for a regex match on the rest of the path.\r
+    if (*path) {\r
+        for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {\r
+            if (re->first->matches(path))\r
+                return re->second;\r
+        }\r
+    }\r
+\r
     return o;\r
 }\r
 \r
@@ -385,8 +453,34 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
     // Load any AccessControl provider.\r
     loadACL(e,log);\r
 \r
+    // Loop over the HostRegex elements.\r
+    const DOMElement* host = XMLHelper::getFirstChildElement(e,HostRegex);\r
+    for (int i=1; host; ++i, host=XMLHelper::getNextSiblingElement(host,HostRegex)) {\r
+        const XMLCh* n=host->getAttributeNS(NULL,regex);\r
+        if (!n || !*n) {\r
+            log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);\r
+            continue;\r
+        }\r
+\r
+        auto_ptr<Override> o(new Override(host,log,this));\r
+\r
+        const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);\r
+        try {\r
+            auto_ptr<RegularExpression> re(\r
+                new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)\r
+                );\r
+            m_regexps.push_back(make_pair(re.release(), o.release()));\r
+        }\r
+        catch (XMLException& ex) {\r
+            auto_ptr_char tmp(ex.getMessage());\r
+            log.error("caught exception while parsing HostRegex regular expression (%d): %s", i, tmp.get());\r
+        }\r
+\r
+        log.debug("Added <HostRegex> mapping for %s", m_regexps.back().second->getString("regex").second);\r
+    }\r
+\r
     // Loop over the Host elements.\r
-    const DOMElement* host = XMLHelper::getFirstChildElement(e,Host);\r
+    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
@@ -509,6 +603,12 @@ const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char
         i=m_extras.find(vhost);\r
         if (i!=m_extras.end())\r
             o=i->second;\r
+        else {\r
+            for (vector< pair<RegularExpression*,Override*> >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) {\r
+                if (re->first->matches(vhost))\r
+                    o=re->second;\r
+            }\r
+        }\r
     }\r
     \r
     return o ? o->locate(path) : this;\r