SSPCPP-616 - fix tuple namespaces and string literal errors
[shibboleth/cpp-sp.git] / shibsp / impl / XMLRequestMapper.cpp
index 9c3a0c4..2b4905d 100644 (file)
@@ -1,17 +1,21 @@
-/*
- *  Copyright 2001-2010 Internet2
+/**
+ * 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.
  *
- * 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
+ * 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
+ * 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.
+ * 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
 #include "AccessControl.h"
 #include "RequestMapper.h"
 #include "SPRequest.h"
+#include "util/CGIParser.h"
 #include "util/DOMPropertySet.h"
 #include "util/SPConstants.h"
 
 #include <algorithm>
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/algorithm/string.hpp>
 #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 boost;
 using namespace std;
 
 namespace shibsp {
@@ -58,9 +71,9 @@ namespace shibsp {
     class Override : public DOMPropertySet, public DOMNodeFilter
     {
     public:
-        Override() : m_acl(NULL) {}
-        Override(const DOMElement* e, Category& log, const Override* base=NULL);
-        ~Override();
+        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
@@ -73,17 +86,18 @@ namespace shibsp {
         }
 
         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.get() : (getParent() ? dynamic_cast<const Override*>(getParent())->getAC() : nullptr)); }
 
     protected:
         void loadACL(const DOMElement* e, Category& log);
 
-        map<string,Override*> m_map;
-        vector< pair<RegularExpression*,Override*> > m_regexps;
-        vector< pair< pair<string,RegularExpression*>,Override*> > m_queries;
+        bool m_unicodeAware;
+        map< string,boost::shared_ptr<Override> > m_map;
+        vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > > m_regexps;
+        vector< boost::tuple< string,boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > > m_queries;
 
     private:
-        AccessControl* m_acl;
+        scoped_ptr<AccessControl> m_acl;
     };
 
     class XMLRequestMapperImpl : public Override
@@ -103,7 +117,6 @@ namespace shibsp {
         const Override* findOverride(const char* vhost, const HTTPRequest& request) const;
 
     private:
-        map<string,Override*> m_extras;
         DOMDocument* m_document;
     };
 
@@ -115,21 +128,21 @@ 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")) {
+            background_load();
         }
 
         ~XMLRequestMapper() {
-            delete m_impl;
+            shutdown();
         }
 
         Settings getSettings(const HTTPRequest& request) const;
 
     protected:
-        pair<bool,DOMElement*> load();
+        pair<bool,DOMElement*> background_load();
 
     private:
-        XMLRequestMapperImpl* m_impl;
+        scoped_ptr<XMLRequestMapperImpl> m_impl;
     };
 
 #if defined (_MSC_VER)
@@ -174,193 +187,174 @@ RequestMapper::~RequestMapper()
 void Override::loadACL(const DOMElement* e, Category& log)
 {
     try {
-        const DOMElement* acl=XMLHelper::getFirstChildElement(e,htaccess);
+        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);
+            m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(HT_ACCESS_CONTROL,acl));
         }
         else {
-            acl=XMLHelper::getFirstChildElement(e,_AccessControl);
+            acl = XMLHelper::getFirstChildElement(e,_AccessControl);
             if (acl) {
                 log.info("building XML-based AccessControl provider...");
-                m_acl=SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl);
+                m_acl.reset(SPConfig::getConfig().AccessControlManager.newPlugin(XML_ACCESS_CONTROL,acl));
             }
             else {
-                acl=XMLHelper::getFirstChildElement(e,AccessControlProvider);
+                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.reset(SPConfig::getConfig().AccessControlManager.newPlugin(t.c_str(), acl));
+                    }
+                    else {
+                        throw ConfigurationException("<AccessControlProvider> missing type attribute.");
+                    }
                 }
             }
         }
     }
-    catch (exception& ex) {
+    catch (std::exception& ex) {
         log.crit("exception building AccessControl provider: %s", ex.what());
-        m_acl = new AccessControlDummy();
+        m_acl.reset(new AccessControlDummy());
     }
 }
 
-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)
 {
-    try {
-        // Load the property set.
-        load(e,NULL,this);
-        setParent(base);
+    // Load the property set.
+    load(e, nullptr, this);
+    setParent(base);
 
-        // Load any AccessControl provider.
-        loadACL(e,log);
+    // 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);
+    // 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++;
+        // 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 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(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;
+        // 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);
             }
+        }
 
-            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;
+        try {
+            boost::shared_ptr<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;
+                log.warn("skipping duplicate Path element (%s)", dup);
+            }
+            else {
+                m_map[dup] = o;
+                log.debug("added Path mapping (%s)", dup);
             }
-            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(NULL,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));
-
-                const XMLCh* flag=path->getAttributeNS(NULL,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);
-            }
+        catch (std::exception&) {
+            free(dup);
+            throw;
         }
+    }
 
-        // 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);
+    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 Query element (%d) with empty name attribute",i);
+                log.warn("skipping PathRegex element (%d) with empty regex attribute",i);
                 continue;
             }
-            auto_ptr_char ntemp(n);
-            const XMLCh* v=path->getAttributeNS(NULL,regex);
 
-            auto_ptr<Override> o(new Override(path,log,this));
+            boost::shared_ptr<Override> o(new Override(m_unicodeAware, path, log, this));
+
+            bool flag = XMLHelper::getAttrBool(path, true, ignoreCase);
             try {
-                RegularExpression* re = NULL;
-                if (v && *v)
-                    re = new RegularExpression(v);
-                m_queries.push_back(make_pair(make_pair(string(ntemp.get()),re), o.release()));
+                boost::shared_ptr<RegularExpression> 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 Query regular expression (%d): %s", i, tmp.get());
-                throw ConfigurationException("Invalid regular expression in Query element.");
+                log.error("caught exception while parsing PathRegex regular expression (%d): %s", i, tmp.get());
+                throw ConfigurationException("Invalid regular expression in PathRegex element.");
             }
 
-            log.debug("added <Query> mapping (%s)", ntemp.get());
+            if (log.isDebugEnabled())
+                log.debug("added <PathRegex> mapping (%s)", o->getString("regex").second);
         }
     }
-    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;
+
+    // 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<Override> o(new Override(m_unicodeAware, path, log, this));
+            boost::shared_ptr<RegularExpression> re((v && *v) ? new RegularExpression(v) : nullptr);
+            m_queries.push_back(boost::make_tuple(string(ntemp.get()), re, o));
         }
-        for (vector< pair< pair<string,RegularExpression*>,Override*> >::iterator j = m_queries.begin(); j != m_queries.end(); ++j) {
-            delete j->first.second;
-            delete j->second;
+        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.");
         }
-        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;
+        log.debug("added <Query> mapping (%s)", ntemp.get());
     }
 }
 
@@ -374,118 +368,123 @@ const Override* Override::locate(const HTTPRequest& request) const
     if (*path == '/')
         path++;
 
-    // Now we copy the path, chop the query string, and lower case it.
-    char* dup=strdup(path);
-    char* sep=strchr(dup,'?');
-    if (sep)
-        *sep=0;
-    for (char* pch=dup; *pch; pch++)
-        *pch=tolower(*pch);
+    // 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;
+    const Override* o = this;
 
     // Tokenize the path by segment and try and map each segment.
-#ifdef HAVE_STRTOK_R
-    char* pos=NULL;
-    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())
+    tokenizer< char_separator<char> > tokens(dup, char_separator<char>("/"));
+    for (tokenizer< char_separator<char> >::iterator token = tokens.begin(); token != tokens.end(); ++token) {
+        map< string,boost::shared_ptr<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;
+        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 += strlen(token);
+        path += token->length();
         if (*path == '/')
             path++;
-
-        // Get the next segment, if any.
-#ifdef HAVE_STRTOK_R
-        token=strtok_r(NULL,"/",&pos);
-#else
-        token=strtok(NULL,"/");
-#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('?'));
+        sep = path2.find('?');
+        if (sep != string::npos)
+            path2 = path2.substr(0, sep);
 
-        for (vector< pair<RegularExpression*,Override*> >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
+        for (vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > >::const_iterator re = o->m_regexps.begin(); re != o->m_regexps.end(); ++re) {
             if (re->first->matches(path2.c_str())) {
-                o = re->second;
+                o = re->second.get();
                 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;
+    // 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<RegularExpression>,boost::shared_ptr<Override> > >::const_iterator q = o->m_queries.begin(); !descended && q != o->m_queries.end(); ++q) {
+                pair<CGIParser::walker,CGIParser::walker> 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->second;
-                    descended = true;
+                    else {
+                        // The simple presence of the parameter is sufficient to match.
+                        o = q->get<2>().get();
+                        descended = true;
+                    }
                 }
             }
-        }
-    } while (descended);
+        } while (descended);
+    }
 
     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 _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,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);
+    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 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);
+            log.warn("Skipping HostRegex element (%d) with empty regex attribute", i);
             continue;
         }
 
-        auto_ptr<Override> o(new Override(host,log,this));
+        boost::shared_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(
+            boost::shared_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()));
+            m_regexps.push_back(make_pair(re, o));
         }
         catch (XMLException& ex) {
             auto_ptr_char tmp(ex.getMessage());
@@ -496,23 +495,21 @@ 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);
+    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);
+            log.warn("Skipping Host element (%d) with empty name attribute", i);
             continue;
         }
 
-        Override* o=new Override(host,log,this);
+        boost::shared_ptr<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);
+        string dup(name.first ? name.second : "");
+        to_lower(dup);
 
         if (!scheme.first && port.first) {
             // No scheme, but a port, so assume http.
@@ -545,65 +542,61 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
                 (!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)) {
+                if (m_map.count(url)) {
                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
-                    delete o;
                     continue;
                 }
-                m_map[url]=o;
+                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.
+                // Now append the port. The shared_ptr should refcount the Override to avoid double deletes.
                 url=url + ':' + port.second;
-                m_extras[url]=o;
+                m_map[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)) {
+                if (m_map.count(url)) {
                     log.warn("Skipping duplicate Host element (%s)",url.c_str());
-                    delete o;
                     continue;
                 }
-                m_map[url]=o;
+                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)) {
+            url += dup;
+            if (m_map.count(url)) {
                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
-                delete o;
                 continue;
             }
-            m_map[url]=o;
+            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)) {
+            url += ":80";
+            if (m_map.count(url)) {
                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
                 continue;
             }
-            m_extras[url]=o;
+            m_map[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)) {
+            url = "https://" + dup;
+            if (m_map.count(url)) {
                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
                 continue;
             }
-            m_extras[url]=o;
+            m_map[url] = o;
             log.debug("Added <Host> mapping for %s",url.c_str());
 
-            url = url + ":443";
-            if (m_map.count(url) || m_extras.count(url)) {
+            url += ":443";
+            if (m_map.count(url)) {
                 log.warn("Skipping duplicate Host element (%s)",url.c_str());
                 continue;
             }
-            m_extras[url]=o;
+            m_map[url] = o;
             log.debug("Added <Host> mapping for %s",url.c_str());
         }
     }
@@ -611,51 +604,50 @@ XMLRequestMapperImpl::XMLRequestMapperImpl(const DOMElement* e, Category& log) :
 
 const Override* XMLRequestMapperImpl::findOverride(const char* vhost, const HTTPRequest& request) const
 {
-    const Override* o=NULL;
-    map<string,Override*>::const_iterator i=m_map.find(vhost);
-    if (i!=m_map.end())
-        o=i->second;
+    const Override* o = nullptr;
+    map< string,boost::shared_ptr<Override> >::const_iterator i = m_map.find(vhost);
+    if (i != m_map.end())
+        o = i->second.get();
     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;
-            }
+        for (vector< pair< boost::shared_ptr<RegularExpression>,boost::shared_ptr<Override> > >::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<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);
+    scoped_ptr<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;
+    // 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*)NULL);
+    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());
+        string normalizedhost(request.getHostname());
+        to_lower(normalizedhost);
+        string vhost = string(request.getScheme()) + "://" + normalizedhost + ':' + lexical_cast<string>(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());