HostRegex and PathRegex support in request map.
authorcantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 24 Jul 2007 19:07:07 +0000 (19:07 +0000)
committercantor <cantor@cb58f699-b61c-0410-a6fe-9272a202ed29>
Tue, 24 Jul 2007 19:07:07 +0000 (19:07 +0000)
git-svn-id: https://svn.middleware.georgetown.edu/cpp-sp/trunk@2371 cb58f699-b61c-0410-a6fe-9272a202ed29

schemas/shibboleth-2.0-native-sp-config.xsd
shibsp/impl/XMLRequestMapper.cpp

index 4a64753..05ffd7e 100644 (file)
                                        <element ref="conf:AccessControl"/>\r
                        <element ref="conf:AccessControlProvider"/>\r
                    </choice>\r
-                <element ref="conf:Host" minOccurs="0" maxOccurs="unbounded"/>\r
+               <choice minOccurs="0" maxOccurs="unbounded">\r
+                                       <element ref="conf:Host"/>\r
+                       <element ref="conf:HostRegex"/>\r
+               </choice>\r
             </sequence>\r
             <attribute name="applicationId" type="conf:string" fixed="default"/>\r
                <attributeGroup ref="conf:ContentSettings"/>\r
                                        <element ref="conf:AccessControl"/>\r
                        <element ref="conf:AccessControlProvider"/>\r
                    </choice>\r
-                       <element ref="conf:Path" minOccurs="0" maxOccurs="unbounded"/>\r
+                       <choice minOccurs="0" maxOccurs="unbounded">\r
+                               <element ref="conf:Path"/>\r
+                               <element ref="conf:PathRegex"/>\r
+                       </choice>\r
                </sequence>\r
                <attribute name="scheme">\r
                            <simpleType>\r
                </attribute>\r
                <attribute name="name" type="conf:string" use="required"/>\r
                <attribute name="port" type="unsignedInt"/>\r
-               <attribute name="applicationId" type="conf:string"/>\r
+                       <attribute name="applicationId" type="conf:string"/>\r
                <attributeGroup ref="conf:ContentSettings"/>\r
        </complexType>\r
     </element>\r
+       \r
+       <element name="HostRegex">\r
+               <complexType>\r
+                       <sequence>\r
+                               <choice minOccurs="0">\r
+                                       <element ref="conf:htaccess"/>\r
+                                       <element ref="conf:AccessControl"/>\r
+                                       <element ref="conf:AccessControlProvider"/>\r
+                               </choice>\r
+                               <choice minOccurs="0" maxOccurs="unbounded">\r
+                                       <element ref="conf:Path"/>\r
+                                       <element ref="conf:PathRegex"/>\r
+                               </choice>\r
+                       </sequence>\r
+                       <attribute name="regex" type="conf:string" use="required"/>\r
+                       <attribute name="ignoreCase" type="boolean" default="true"/>\r
+                       <attribute name="applicationId" type="conf:string"/>\r
+                       <attributeGroup ref="conf:ContentSettings"/>\r
+               </complexType>\r
+       </element>\r
 \r
     <element name="Path">\r
         <complexType>\r
                                        <element ref="conf:AccessControl"/>\r
                        <element ref="conf:AccessControlProvider"/>\r
                    </choice>\r
-                       <element ref="conf:Path" minOccurs="0" maxOccurs="unbounded"/>\r
+                       <choice minOccurs="0" maxOccurs="unbounded">\r
+                               <element ref="conf:Path"/>\r
+                               <element ref="conf:PathRegex"/>\r
+                       </choice>\r
                </sequence>\r
-               <attribute name="name" type="conf:string" use="required"/>\r
-               <attribute name="applicationId" type="conf:string"/>\r
-               <attributeGroup ref="conf:ContentSettings"/>\r
+                       <attribute name="name" type="conf:string" use="required"/>\r
+                       <attribute name="applicationId" type="conf:string"/>\r
+                       <attributeGroup ref="conf:ContentSettings"/>\r
         </complexType>\r
     </element>\r
+\r
+       <element name="PathRegex">\r
+               <complexType>\r
+                       <sequence>\r
+                               <choice minOccurs="0">\r
+                                       <element ref="conf:htaccess"/>\r
+                                       <element ref="conf:AccessControl"/>\r
+                                       <element ref="conf:AccessControlProvider"/>\r
+                               </choice>\r
+                       </sequence>\r
+                       <attribute name="regex" type="conf:string" use="required"/>\r
+                       <attribute name="ignoreCase" type="boolean" default="true"/>\r
+                       <attribute name="applicationId" type="conf:string"/>\r
+                       <attributeGroup ref="conf:ContentSettings"/>\r
+               </complexType>\r
+       </element>\r
        \r
        <element name="Applications">\r
                <annotation>\r
index 5cfe91d..2af0e2c 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
@@ -30,6 +31,7 @@
 #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
@@ -78,6 +80,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,12 +142,17 @@ 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 regex[] =                    UNICODE_LITERAL_5(r,e,g,e,x);\r
     static const XMLCh _type[] =                    UNICODE_LITERAL_4(t,y,p,e);\r
 }\r
 \r
@@ -275,8 +283,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 +327,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 +383,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 +430,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 +454,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 +604,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