X-Git-Url: http://www.project-moonshot.org/gitweb/?p=shibboleth%2Fcpp-sp.git;a=blobdiff_plain;f=shibsp%2Fimpl%2FXMLRequestMapper.cpp;h=2b4905d2b1a8cf78729b99ebbb519cc55620ede4;hp=d3569239d4e2335fa950b69ec087ab324c88e8e8;hb=f0cab28136e0758f7bf7c60581d714f5f5dbbf08;hpb=07a42e21868d4484d7e070cb82fa9de777ddb175 diff --git a/shibsp/impl/XMLRequestMapper.cpp b/shibsp/impl/XMLRequestMapper.cpp index d356923..2b4905d 100644 --- a/shibsp/impl/XMLRequestMapper.cpp +++ b/shibsp/impl/XMLRequestMapper.cpp @@ -1,551 +1,657 @@ -/* - * 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 "AccessControl.h" -#include "RequestMapper.h" -#include "SPRequest.h" -#include "util/DOMPropertySet.h" -#include "util/SPConstants.h" - -#include -#include -#include -#include - -using namespace shibsp; -using namespace xmltooling; -using namespace log4cpp; -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() {} - - bool authorized(const SPRequest& request, const Session* session) const { - return false; - } - }; - - class Override : public DOMPropertySet, public DOMNodeFilter - { - public: - Override() : m_base(NULL), m_acl(NULL) {} - Override(const DOMElement* e, Category& log, const Override* base=NULL); - ~Override(); - - // PropertySet - pair getBool(const char* name, const char* ns=NULL) const; - pair getString(const char* name, const char* ns=NULL) const; - pair getXMLString(const char* name, const char* ns=NULL) const; - pair getUnsignedInt(const char* name, const char* ns=NULL) const; - pair getInt(const char* name, const char* ns=NULL) const; - const PropertySet* getPropertySet(const char* name, const char* ns="urn:mace:shibboleth:target:config:1.0") const; - - // Provides filter to exclude special config elements. - short acceptNode(const DOMNode* node) const; - - const Override* locate(const char* path) const; - AccessControl* getAC() const { return (m_acl ? m_acl : (m_base ? m_base->getAC() : NULL)); } - - protected: - void loadACL(const DOMElement* e, Category& log); - - map m_map; - - private: - const Override* m_base; - 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 char* path) const; - - private: - map 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), m_impl(NULL), m_log(Category::getInstance(SHIBSP_LOGCAT".RequestMapper")) { - load(); - } - - ~XMLRequestMapper() { - delete m_impl; - } - - Settings getSettings(const SPRequest& request) const; - - protected: - pair load(); - - private: - XMLRequestMapperImpl* m_impl; - Category& m_log; - }; - -#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 htaccess[] = UNICODE_LITERAL_8(h,t,a,c,c,e,s,s); - static const XMLCh Host[] = UNICODE_LITERAL_4(H,o,s,t); - static const XMLCh Path[] = UNICODE_LITERAL_4(P,a,t,h); - static const XMLCh name[] = UNICODE_LITERAL_4(n,a,m,e); - 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); -} - -short Override::acceptNode(const DOMNode* node) const -{ - if (!XMLString::equals(node->getNamespaceURI(),shibspconstants::SHIB1SPCONFIG_NS)) - return FILTER_ACCEPT; - const XMLCh* name=node->getLocalName(); - if (XMLString::equals(name,Host) || - XMLString::equals(name,Path) || - XMLString::equals(name,_AccessControl) || - XMLString::equals(name,htaccess) || - XMLString::equals(name,AccessControlProvider)) - return FILTER_REJECT; - - return FILTER_ACCEPT; -} - -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) { - xmltooling::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_base(base), m_acl(NULL) -{ - try { - // Load the property set. - load(e,log,this); - - // 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::SHIB1SPCONFIG_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 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; - free(dup); - } - } - catch (exception&) { - delete m_acl; - for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair()); - throw; - } -} - -Override::~Override() -{ - delete m_acl; - for_each(m_map.begin(),m_map.end(),xmltooling::cleanup_pair()); -} - -pair Override::getBool(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getBool(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getBool(name,ns) : ret; -} - -pair Override::getString(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getString(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getString(name,ns) : ret; -} - -pair Override::getXMLString(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getXMLString(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getXMLString(name,ns) : ret; -} - -pair Override::getUnsignedInt(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getUnsignedInt(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getUnsignedInt(name,ns) : ret; -} - -pair Override::getInt(const char* name, const char* ns) const -{ - pair ret=DOMPropertySet::getInt(name,ns); - if (ret.first) - return ret; - return m_base ? m_base->getInt(name,ns) : ret; -} - -const PropertySet* Override::getPropertySet(const char* name, const char* ns) const -{ - const PropertySet* ret=DOMPropertySet::getPropertySet(name,ns); - if (ret || !m_base) - return ret; - return m_base->getPropertySet(name,ns); -} - -const Override* Override::locate(const char* path) const -{ - char* dup=strdup(path); - char* sep=strchr(dup,'?'); - if (sep) - *sep=0; - for (char* pch=dup; *pch; pch++) - *pch=tolower(*pch); - - const Override* o=this; - -#ifdef HAVE_STRTOK_R - char* pos=NULL; - const char* token=strtok_r(dup,"/",&pos); -#else - const char* token=strtok(dup,"/"); -#endif - while (token) - { - map::const_iterator i=o->m_map.find(token); - if (i==o->m_map.end()) - break; - o=i->second; -#ifdef HAVE_STRTOK_R - token=strtok_r(NULL,"/",&pos); -#else - token=strtok(NULL,"/"); -#endif - } - - free(dup); - 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,log,this); - - // Load any AccessControl provider. - loadACL(e,log); - - // Loop over the Host elements. - const DOMElement* 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 name=o->getString("name"); - pair scheme=o->getString("scheme"); - pair port=o->getString("port"); - - char* dup=strdup(name.second); - for (char* pch=dup; *pch; pch++) - *pch=tolower(*pch); - auto_ptr dupwrap(dup); - - if (!scheme.first && port.first) { - // No scheme, but a port, so assume http. - scheme = pair(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 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 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 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 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 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 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 mapping for %s",url.c_str()); - } - } -} - -const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const char* path) const -{ - const Override* o=NULL; - map::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; - } - - return o ? o->locate(path) : this; -} - -pair XMLRequestMapper::load() -{ - // Load from source using base class. - pair raw = ReloadableXMLFile::load(); - - // If we own it, wrap it. - XercesJanitor 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 SPRequest& request) const -{ - ostringstream vhost; - vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort(); - - const Override* o=m_impl->findOverride(vhost.str().c_str(), request.getRequestURI()); - - if (m_log.isDebugEnabled()) { -#ifdef _DEBUG - xmltooling::NDC ndc("getSettings"); -#endif - pair 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()); -} +/** + * Licensed to the University Corporation for Advanced Internet + * Development, Inc. (UCAID) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * UCAID licenses this file to you 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/CGIParser.h" +#include "util/DOMPropertySet.h" +#include "util/SPConstants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using shibspconstants::SHIB2SPCONFIG_NS; +using namespace shibsp; +using namespace xmltooling; +using namespace boost; +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) {} + 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.get() : (getParent() ? dynamic_cast(getParent())->getAC() : nullptr)); } + + protected: + void loadACL(const DOMElement* e, Category& log); + + bool m_unicodeAware; + map< string,boost::shared_ptr > m_map; + vector< pair< boost::shared_ptr,boost::shared_ptr > > m_regexps; + vector< boost::tuple< string,boost::shared_ptr,boost::shared_ptr > > m_queries; + + private: + scoped_ptr 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: + 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")) { + background_load(); + } + + ~XMLRequestMapper() { + shutdown(); + } + + Settings getSettings(const HTTPRequest& request) const; + + protected: + pair background_load(); + + private: + scoped_ptr 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.reset(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.reset(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.reset(SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acl)); + } + else { + throw ConfigurationException(" missing type attribute."); + } + } + } + } + } + catch (std::exception& ex) { + log.crit("exception building AccessControl provider: %s", ex.what()); + m_acl.reset(new AccessControlDummy()); + } +} + +Override::Override(bool unicodeAware, const DOMElement* e, Category& log, const Override* base) + : m_unicodeAware(unicodeAware) +{ + // 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. + xstring namebuf; + for (int pos = 0; pos < slash; ++pos) + namebuf += n[pos]; + + // 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.c_str()); + 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.c_str()); + n = path->getAttributeNS(nullptr, name); + } + } + + char* dup = nullptr; + try { + boost::shared_ptr 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); + } + else { + m_map[dup] = o; + log.debug("added Path mapping (%s)", dup); + } + free(dup); + } + catch (std::exception&) { + free(dup); + throw; + } + } + + 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; + } + + boost::shared_ptr o(new Override(m_unicodeAware, path, log, this)); + + bool flag = XMLHelper::getAttrBool(path, true, ignoreCase); + try { + boost::shared_ptr re(new RegularExpression(n, flag ? &chNull : ignoreOption)); + m_regexps.push_back(make_pair(re, o)); + } + 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 mapping (%s)", o->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); + + try { + boost::shared_ptr o(new Override(m_unicodeAware, path, log, this)); + boost::shared_ptr re((v && *v) ? new RegularExpression(v) : nullptr); + m_queries.push_back(boost::make_tuple(string(ntemp.get()), re, o)); + } + 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 mapping (%s)", ntemp.get()); + } +} + +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. + string dup(path); + string::size_type sep = dup.find('?'); + if (sep != string::npos) + dup = dup.substr(0, sep); + if (!m_unicodeAware) { + to_lower(dup); + } + + // Default is for the current object to provide settings. + const Override* o = this; + + // Tokenize the path by segment and try and map each segment. + tokenizer< char_separator > tokens(dup, char_separator("/")); + for (tokenizer< char_separator >::iterator token = tokens.begin(); token != tokens.end(); ++token) { + map< string,boost::shared_ptr >::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.get(); + + // We descended a step down the path, so we need to advance the original + // parameter for the regex step later. + path += token->length(); + if (*path == '/') + path++; + } + + // 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); + sep = path2.find('?'); + if (sep != string::npos) + path2 = path2.substr(0, sep); + + for (vector< pair< boost::shared_ptr,boost::shared_ptr > >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) { + if (re->first->matches(path2.c_str())) { + o = re->second.get(); + break; + } + } + } + + // Finally, check for query string matches. This is another "unrolled" recursive descent in a loop. + // To avoid consuming any POST data, we use a dedicated CGIParser. + if (!o->m_queries.empty()) { + bool descended; + CGIParser cgi(request, true); + do { + descended = false; + for (vector< boost::tuple< string,boost::shared_ptr,boost::shared_ptr > >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) { + pair vals = cgi.getParameters(q->get<0>().c_str()); + if (vals.first != vals.second) { + if (q->get<1>()) { + // We have to match one of the values. + while (vals.first != vals.second) { + if (q->get<1>()->matches(vals.first->second)) { + o = q->get<2>().get(); + descended = true; + break; + } + ++vals.first; + } + } + else { + // The simple presence of the parameter is sufficient to match. + o = q->get<2>().get(); + 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 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; + } + + boost::shared_ptr o(new Override(m_unicodeAware, host, log, this)); + + const XMLCh* flag = host->getAttributeNS(nullptr,ignoreCase); + try { + boost::shared_ptr re( + new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption) + ); + m_regexps.push_back(make_pair(re, o)); + } + 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 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; + } + + boost::shared_ptr o(new Override(m_unicodeAware, host, log, this)); + pair name=o->getString("name"); + pair scheme=o->getString("scheme"); + pair port=o->getString("port"); + + string dup(name.first ? name.second : ""); + to_lower(dup); + + if (!scheme.first && port.first) { + // No scheme, but a port, so assume http. + scheme = pair(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)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + + // Now append the port. The shared_ptr should refcount the Override to avoid double deletes. + url=url + ':' + port.second; + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + } + else { + url=url + ':' + port.second; + if (m_map.count(url)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added 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 += dup; + if (m_map.count(url)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + + url += ":80"; + if (m_map.count(url)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + + url = "https://" + dup; + if (m_map.count(url)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + + url += ":443"; + if (m_map.count(url)) { + log.warn("Skipping duplicate Host element (%s)",url.c_str()); + continue; + } + m_map[url] = o; + log.debug("Added mapping for %s",url.c_str()); + } + } +} + +const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const +{ + const Override* o = nullptr; + map< string,boost::shared_ptr >::const_iterator i = m_map.find(vhost); + if (i != m_map.end()) + o = i->second.get(); + else { + for (vector< pair< boost::shared_ptr,boost::shared_ptr > >::const_iterator re = m_regexps.begin(); !o && re != m_regexps.end(); ++re) { + if (re->first->matches(vhost)) + o=re->second.get(); + } + } + + return o ? o->locate(request) : this; +} + +pair XMLRequestMapper::background_load() +{ + // Load from source using base class. + pair raw = ReloadableXMLFile::load(); + + // If we own it, wrap it. + XercesJanitor docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr); + + scoped_ptr 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); + m_impl.swap(impl); + + return make_pair(false,(DOMElement*)nullptr); +} + +RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const +{ + try { + string normalizedhost(request.getHostname()); + to_lower(normalizedhost); + string vhost = string(request.getScheme()) + "://" + normalizedhost + ':' + lexical_cast(request.getPort()); + const Override* o = m_impl->findOverride(vhost.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."); + } +}