https://bugs.internet2.edu/jira/browse/SSPCPP-287
[shibboleth/sp.git] / shibsp / impl / XMLRequestMapper.cpp
index 9c5f294..9e0657f 100644 (file)
@@ -1,6 +1,6 @@
 /*
- *  Copyright 2001-2007 Internet2
- * 
+ *  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
@@ -15,8 +15,8 @@
  */
 
 /** XMLRequestMapper.cpp
- * 
- * XML-based RequestMapper implementation
+ *
+ * XML-based RequestMapper implementation.
  */
 
 #include "internal.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. 
+    // 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;
         }
@@ -57,25 +60,31 @@ namespace shibsp {
     class Override : public DOMPropertySet, public DOMNodeFilter
     {
     public:
-        Override() : m_acl(NULL) {}
-        Override(const DOMElement* e, Category& log, const Override* base=NULL);
+        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.
-        short acceptNode(const DOMNode* node) const {
+#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() : NULL)); }
-        
+        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;
     };
@@ -93,10 +102,10 @@ namespace shibsp {
         void setDocument(DOMDocument* doc) {
             m_document = doc;
         }
-    
+
         const Override* findOverride(const char* vhost, const HTTPRequest& request) const;
 
-    private:    
+    private:
         map<string,Override*> m_extras;
         DOMDocument* m_document;
     };
@@ -109,18 +118,19 @@ namespace shibsp {
     class XMLRequestMapper : public RequestMapper, public ReloadableXMLFile
     {
     public:
-        XMLRequestMapper(const DOMElement* e) : ReloadableXMLFile(e,Category::getInstance(SHIBSP_LOGCAT".RequestMapper")), m_impl(NULL) {
-            load();
+        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*> load();
+        pair<bool,DOMElement*> background_load();
 
     private:
         XMLRequestMapperImpl* m_impl;
@@ -157,6 +167,14 @@ void SHIBSP_API shibsp::registerRequestMappers()
     conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
 }
 
+RequestMapper::RequestMapper()
+{
+}
+
+RequestMapper::~RequestMapper()
+{
+}
+
 void Override::loadACL(const DOMElement* e, Category& log)
 {
     try {
@@ -174,9 +192,14 @@ void Override::loadACL(const DOMElement* e, Category& log)
             else {
                 acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
                 if (acl) {
-                    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);
+                    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.");
+                    }
                 }
             }
         }
@@ -187,25 +210,26 @@ void Override::loadACL(const DOMElement* e, Category& log)
     }
 }
 
-Override::Override(const DOMElement* e, Category& log, const Override* base) : m_acl(NULL)
+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,NULL,this);
+        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(NULL,name);
-            
+            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);
@@ -220,46 +244,51 @@ Override::Override(const DOMElement* e, Category& log, const Override* base) : m
                 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(NULL,name,namebuf);
-                    path->setAttributeNS(NULL,name,n);
+                    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(NULL,name);
+                    n=path->getAttributeNS(nullptr,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);
+                    path->setAttributeNS(nullptr,name,namebuf);
+                    n=path->getAttributeNS(nullptr,name);
                 }
                 delete[] namebuf;
             }
-            
-            Override* o=new Override(path,log,this);
-            pair<bool,const char*> name=o->getString("name");
-            char* dup=strdup(name.second);
-            for (char* pch=dup; *pch; pch++)
-                *pch=tolower(*pch);
+
+            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;
+            m_map[dup] = o;
             log.debug("added Path mapping (%s)", dup);
             free(dup);
         }
@@ -268,15 +297,15 @@ Override::Override(const DOMElement* e, Category& log, const Override* base) : m
             // Handle nested PathRegexs.
             path = XMLHelper::getFirstChildElement(e,PathRegex);
             for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,PathRegex)) {
-                const XMLCh* n=path->getAttributeNS(NULL,regex);
+                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(path,log,this));
+                auto_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
 
-                const XMLCh* flag=path->getAttributeNS(NULL,ignoreCase);
+                const XMLCh* flag=path->getAttributeNS(nullptr,ignoreCase);
                 try {
                     auto_ptr<RegularExpression> re(
                         new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
@@ -297,17 +326,17 @@ Override::Override(const DOMElement* e, Category& log, const Override* base) : m
         // Handle nested Querys.
         path = XMLHelper::getFirstChildElement(e,Query);
         for (int i=1; path; ++i, path=XMLHelper::getNextSiblingElement(path,Query)) {
-            const XMLCh* n=path->getAttributeNS(NULL,name);
+            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(NULL,regex);
+            const XMLCh* v=path->getAttributeNS(nullptr,regex);
 
-            auto_ptr<Override> o(new Override(path,log,this));
+            auto_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
             try {
-                RegularExpression* re = NULL;
+                RegularExpression* re = nullptr;
                 if (v && *v)
                     re = new RegularExpression(v);
                 m_queries.push_back(make_pair(make_pair(string(ntemp.get()),re), o.release()));
@@ -317,7 +346,7 @@ Override::Override(const DOMElement* e, Category& log, const Override* base) : m
                 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());
         }
     }
@@ -360,20 +389,22 @@ const Override* Override::locate(const HTTPRequest& request) const
     if (*path == '/')
         path++;
 
-    // Now we copy the path, chop the query string, and lower case it.
+    // 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;
-    for (char* pch=dup; *pch; pch++)
-        *pch=tolower(*pch);
+    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=NULL;
+    char* pos=nullptr;
     const char* token=strtok_r(dup,"/",&pos);
 #else
     const char* token=strtok(dup,"/");
@@ -384,7 +415,7 @@ const Override* Override::locate(const HTTPRequest& request) const
             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);
@@ -393,9 +424,9 @@ const Override* Override::locate(const HTTPRequest& request) const
 
         // Get the next segment, if any.
 #ifdef HAVE_STRTOK_R
-        token=strtok_r(NULL,"/",&pos);
+        token=strtok_r(nullptr,"/",&pos);
 #else
-        token=strtok(NULL,"/");
+        token=strtok(nullptr,"/");
 #endif
     }
 
@@ -443,30 +474,43 @@ const Override* Override::locate(const HTTPRequest& request) const
     return o;
 }
 
-XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(NULL)
+XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) : m_document(nullptr)
 {
 #ifdef _DEBUG
     xmltooling::NDC ndc("XMLRequestMapperImpl");
 #endif
+    static const XMLCh _default[] =     UNICODE_LITERAL_7(d,e,f,a,u,l,t);
+    static const XMLCh _id[] =          UNICODE_LITERAL_2(i,d);
+    static const XMLCh _RequestMap[] =  UNICODE_LITERAL_10(R,e,q,u,e,s,t,M,a,p);
+
+    if (!XMLHelper::isNodeNamed(e, SHIB2SPCONFIG_NS, _RequestMap))
+        throw ConfigurationException("XML RequestMapper requires conf:RequestMap at root of configuration.");
 
     // Load the property set.
-    load(e,NULL,this);
-    
+    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(NULL,regex);
+        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(host,log,this));
+        auto_ptr<Override> o(new Override(m_unicodeAware, host, log, this));
 
-        const XMLCh* flag=host->getAttributeNS(NULL,ignoreCase);
+        const XMLCh* flag=host->getAttributeNS(nullptr,ignoreCase);
         try {
             auto_ptr<RegularExpression> re(
                 new RegularExpression(n, (flag && (*flag==chLatin_f || *flag==chDigit_0)) ? &chNull : ignoreOption)
@@ -484,17 +528,17 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
     // 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(NULL,name);
+        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(host,log,this);
+
+        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);
@@ -523,7 +567,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
         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")) ||
@@ -538,7 +582,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
                 }
                 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;
@@ -566,7 +610,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
             }
             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());
@@ -574,7 +618,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
             }
             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)) {
@@ -583,7 +627,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
             }
             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());
@@ -597,7 +641,7 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
 
 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const
 {
-    const Override* o=NULL;
+    const Override* o=nullptr;
     map<string,Override*>::const_iterator i=m_map.find(vhost);
     if (i!=m_map.end())
         o=i->second;
@@ -612,43 +656,44 @@ const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTP
             }
         }
     }
-    
+
     return o ? o->locate(request) : this;
 }
 
-pair<bool,DOMElement*> XMLRequestMapper::load()
+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() : NULL);
+    XercesJanitor<DOMDocument> docjanitor(raw.first ? raw.second->getOwnerDocument() : nullptr);
+
+    XMLRequestMapperImpl* impl = new XMLRequestMapperImpl(raw.second, m_log);
 
-    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*)NULL);
+    return make_pair(false,(DOMElement*)nullptr);
 }
 
 RequestMapper::Settings XMLRequestMapper::getSettings(const HTTPRequest& request) const
 {
-    ostringstream vhost;
-    vhost << request.getScheme() << "://" << request.getHostname() << ':' << request.getPort();
-
-    const Override* o=m_impl->findOverride(vhost.str().c_str(), request);
-
-    if (m_log.isDebugEnabled()) {
-#ifdef _DEBUG
-        xmltooling::NDC ndc("getSettings");
-#endif
-        pair<bool,const char*> ret=o->getString("applicationId");
-        m_log.debug("mapped %s%s to %s", vhost.str().c_str(), request.getRequestURI() ? request.getRequestURI() : "", ret.second);
+    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.");
     }
-
-    return Settings(o,o->getAC());
 }