<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
*/\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
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
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
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
{\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
\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
}\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
// 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
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