/*
- * 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
*/
/** 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;
}
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;
};
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;
};
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;
conf.RequestMapperManager.registerFactory(NATIVE_REQUEST_MAPPER, XMLRequestMapperFactory);
}
+RequestMapper::RequestMapper()
+{
+}
+
+RequestMapper::~RequestMapper()
+{
+}
+
void Override::loadACL(const DOMElement* e, Category& log)
{
try {
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.");
+ }
}
}
}
}
}
-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);
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);
}
// 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)
// 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()));
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());
}
}
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,"/");
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);
// 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
}
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)
// 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);
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")) ||
}
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;
}
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());
}
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)) {
}
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());
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;
}
}
}
-
+
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());
}