-/*\r
- * Copyright 2001-2005 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(SPRequest& request, 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-2010 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 <algorithm>
+#include <xmltooling/util/NDC.h>
+#include <xmltooling/util/ReloadableXMLFile.h>
+#include <xmltooling/util/Threads.h>
+#include <xmltooling/util/XMLHelper.h>
+#include <xercesc/util/XMLUniDefs.hpp>
+#include <xercesc/util/regx/RegularExpression.hpp>
+
+using shibspconstants::SHIB2SPCONFIG_NS;
+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(bool unicodeAware=false) : m_unicodeAware(unicodeAware), m_acl(nullptr) {}
+ Override(bool unicodeAware, const DOMElement* e, Category& log, const Override* base=nullptr);
+ ~Override();
+
+ // Provides filter to exclude special config elements.
+#ifdef SHIBSP_XERCESC_SHORT_ACCEPTNODE
+ short
+#else
+ FilterAction
+#endif
+ 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() : nullptr)); }
+
+ protected:
+ void loadACL(const DOMElement* e, Category& log);
+
+ bool m_unicodeAware;
+ 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(nullptr) {
+ background_load();
+ }
+
+ ~XMLRequestMapper() {
+ shutdown();
+ delete m_impl;
+ }
+
+ Settings getSettings(const HTTPRequest& request) const;
+
+ protected:
+ pair<bool,DOMElement*> background_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);
+}
+
+RequestMapper::RequestMapper()
+{
+}
+
+RequestMapper::~RequestMapper()
+{
+}
+
+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) {
+ string t(XMLHelper::getAttrString(acl, nullptr, _type));
+ if (!t.empty()) {
+ log.info("building AccessControl provider of type %s...", t.c_str());
+ m_acl = SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acl);
+ }
+ else {
+ throw ConfigurationException("<AccessControlProvider> missing type attribute.");
+ }
+ }
+ }
+ }
+ }
+ catch (exception& ex) {
+ log.crit("exception building AccessControl provider: %s", ex.what());
+ m_acl = new AccessControlDummy();
+ }
+}
+
+Override::Override(bool unicodeAware, const DOMElement* e, Category& log, const Override* base)
+ : m_unicodeAware(unicodeAware), m_acl(nullptr)
+{
+ try {
+ // Load the property set.
+ load(e,nullptr,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(nullptr,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(nullptr,name,namebuf);
+ path->setAttributeNS(nullptr,name,n);
+ path->getParentNode()->replaceChild(newpath,path);
+ newpath->appendChild(path);
+
+ // Repoint our locals at the new parent.
+ path=newpath;
+ n=path->getAttributeNS(nullptr,name);
+ }
+ else {
+ // All we had was a pathname with trailing slash(es), so just reset it without them.
+ path->setAttributeNS(nullptr,name,namebuf);
+ n=path->getAttributeNS(nullptr,name);
+ }
+ delete[] namebuf;
+ }
+
+ char* dup = nullptr;
+ Override* o = new Override(m_unicodeAware, path, log, this);
+ if (m_unicodeAware) {
+ dup = toUTF8(o->getXMLString("name").second, true /* use malloc */);
+ }
+ else {
+ dup = strdup(o->getString("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(nullptr,regex);
+ if (!n || !*n) {
+ log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
+ continue;
+ }
+
+ auto_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
+
+ const XMLCh* flag=path->getAttributeNS(nullptr,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(nullptr,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(nullptr,regex);
+
+ auto_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
+ try {
+ RegularExpression* re = nullptr;
+ 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 possibly lower case it.
+ char* dup=strdup(path);
+ char* sep=strchr(dup,'?');
+ if (sep)
+ *sep=0;
+ if (!m_unicodeAware) {
+ 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=nullptr;
+ 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(nullptr,"/",&pos);
+#else
+ token=strtok(nullptr,"/");
+#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(nullptr)
+{
+#ifdef _DEBUG
+ xmltooling::NDC ndc("XMLRequestMapperImpl");
+#endif
+ static const XMLCh _RequestMap[] = UNICODE_LITERAL_10(R,e,q,u,e,s,t,M,a,p);
+
+ if (e && !XMLHelper::isNodeNamed(e, SHIB2SPCONFIG_NS, _RequestMap))
+ throw ConfigurationException("XML RequestMapper requires conf:RequestMap at root of configuration.");
+
+ // Load the property set.
+ load(e,nullptr,this);
+
+ // Inject "default" app ID if not explicit.
+ if (!getString("applicationId").first)
+ setProperty("applicationId", "default");
+
+ // Load any AccessControl provider.
+ loadACL(e,log);
+
+ pair<bool,bool> unicodeAware = getBool("unicodeAware");
+ m_unicodeAware = (unicodeAware.first && unicodeAware.second);
+
+ // 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(nullptr,regex);
+ if (!n || !*n) {
+ log.warn("Skipping HostRegex element (%d) with empty regex attribute",i);
+ continue;
+ }
+
+ auto_ptr<Override> o(new Override(m_unicodeAware, host, log, this));
+
+ const XMLCh* flag=host->getAttributeNS(nullptr,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(nullptr,name);
+ if (!n || !*n) {
+ log.warn("Skipping Host element (%d) with empty name attribute",i);
+ continue;
+ }
+
+ Override* o=new Override(m_unicodeAware, 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=nullptr;
+ 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::background_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() : nullptr);
+
+ 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());
+
+ // Perform the swap inside a lock.
+ if (m_lock)
+ m_lock->wrlock();
+ SharedLock locker(m_lock, false);
+ delete m_impl;
+ m_impl = impl;
+
+ return make_pair(false,(DOMElement*)nullptr);
+}
+
+RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const
+{
+ try {
+ ostringstream vhost;
+ vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();
+ const Override* o=m_impl->findOverride(vhost.str().c_str(), request);
+ return Settings(o,o->getAC());
+ }
+ catch (XMLException& ex) {
+ auto_ptr_char tmp(ex.getMessage());
+ m_log.error("caught exception while locating content settings: %s", tmp.get());
+ throw ConfigurationException("XML-based RequestMapper failed to retrieve content settings.");
+ }
+}