blob: 36c09f2f303e86c87a6230595ec01b86c813513b [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.
*/
/*
* XSEC
*
* TXFMXPath := Class that performs XPath transforms
*
* $Id$
*
*/
#include <xsec/dsig/DSIGConstants.hpp>
#include <xsec/framework/XSECError.hpp>
#include <xsec/transformers/TXFMXPath.hpp>
#include <xsec/transformers/TXFMParser.hpp>
#ifdef XSEC_HAVE_XALAN
#include "../utils/XSECDOMUtils.hpp"
#if defined(_MSC_VER)
# pragma warning(disable: 4267)
#endif
#include <xalanc/XPath/XObjectFactoryDefault.hpp>
#include <xalanc/XPath/XPathExecutionContextDefault.hpp>
#if defined(_MSC_VER)
# pragma warning(default: 4267)
#endif
// If this isn't defined, we're on Xalan 1.12+ and require modern C++
#ifndef XALAN_USING_XALAN
# define XALAN_USING_XALAN(NAME) using xalanc :: NAME;
#endif
// Xalan namespace usage
XALAN_USING_XALAN(XPathProcessorImpl)
XALAN_USING_XALAN(XercesDOMSupport)
XALAN_USING_XALAN(XercesParserLiaison)
XALAN_USING_XALAN(XercesDocumentWrapper)
XALAN_USING_XALAN(XercesWrapperNavigator)
XALAN_USING_XALAN(XPathEvaluator)
XALAN_USING_XALAN(XPathFactoryDefault)
XALAN_USING_XALAN(XPathConstructionContextDefault)
XALAN_USING_XALAN(XalanDocument)
XALAN_USING_XALAN(XalanNode)
XALAN_USING_XALAN(XalanDOMChar)
XALAN_USING_XALAN(XPathEnvSupportDefault)
XALAN_USING_XALAN(XObjectFactoryDefault)
XALAN_USING_XALAN(XPathExecutionContextDefault)
XALAN_USING_XALAN(ElementPrefixResolverProxy)
XALAN_USING_XALAN(XPath)
XALAN_USING_XALAN(NodeRefListBase)
XALAN_USING_XALAN(XSLTResultTarget)
XALAN_USING_XALAN(XSLException)
#endif
XERCES_CPP_NAMESPACE_USE
#ifdef XSEC_HAVE_XPATH
#include <iostream>
#define KLUDGE_PREFIX "berindsig"
// Helper function
void setXPathNS(DOMDocument *d,
DOMNamedNodeMap *xAtts,
XSECXPathNodeList &addedNodes,
XSECSafeBufferFormatter *formatter,
XSECNameSpaceExpander * nse) {
// if set then set the name spaces in the attribute list else clear them
DOMElement * e = d->getDocumentElement();
if (e == NULL) {
throw XSECException(XSECException::XPathError, "Element node not found in Document");
}
if (xAtts != 0) {
int xAttsCount = xAtts->getLength();
// Check all is OK with the Xalan Document and first element
if (d == NULL) {
throw XSECException(XSECException::XPathError, "Attempt to define XPath Name Space before setInput called");
}
// Run through each attribute looking for name spaces
const XMLCh *xpName;
safeBuffer xpNameSB;
const XMLCh *xpLocalName;
const XMLCh *xpValue;
for (int xCounter = 0; xCounter < xAttsCount; ++xCounter) {
if (nse == NULL || !nse->nodeWasAdded(xAtts->item(xCounter))) {
xpName = xAtts->item(xCounter)->getNodeName();
xpNameSB << (*formatter << xpName);
if (xpNameSB.sbStrncmp("xmlns", 5) == 0) {
// Check whether a node of this name already exists
xpLocalName = xAtts->item(xCounter)->getLocalName();
xpValue = xAtts->item(xCounter)->getNodeValue();
if (e->hasAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS, xpLocalName) == false) {
// Nope
e->setAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS, xpName, xpValue);
addedNodes.addNode(e->getAttributeNodeNS(DSIGConstants::s_unicodeStrURIXMLNS, xpLocalName));
}
}
}
}
}
// Insert the kludge namespace
safeBuffer k("xmlns:");
k.sbStrcatIn(KLUDGE_PREFIX);
e->setAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS,
MAKE_UNICODE_STRING(k.rawCharBuffer()),
DSIGConstants::s_unicodeStrURIDSIG);
}
void clearXPathNS(DOMDocument *d,
XSECXPathNodeList &toRemove,
XSECSafeBufferFormatter *formatter,
XSECNameSpaceExpander * nse) {
// Clear the XPath name spaces in the document element attribute list
DOMElement * e = d->getDocumentElement();
if (e == NULL) {
throw XSECException(XSECException::XPathError, "Element node not found in Document");
}
// Run through each node in the added nodes
const DOMNode * r = toRemove.getFirstNode();
while (r != NULL) {
e->removeAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS,
r->getLocalName());
r = toRemove.getNextNode();
}
e->removeAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS,
MAKE_UNICODE_STRING(KLUDGE_PREFIX));
}
TXFMXPath::TXFMXPath(DOMDocument *doc) :
TXFMBase(doc) {
document = NULL;
XPathAtts = NULL;
// Formatter is used for handling attribute name space inputs
XSECnew(formatter, XSECSafeBufferFormatter("UTF-8",XMLFormatter::NoEscapes,
XMLFormatter::UnRep_CharRef));
}
TXFMXPath::~TXFMXPath() {
if (formatter != NULL)
delete formatter;
}
void TXFMXPath::setNameSpace(DOMNamedNodeMap *xpAtts) {
// A name space needs to be set on the document
XPathAtts = xpAtts;
}
// Methods to set the inputs
void TXFMXPath::setInput(TXFMBase *newInput) {
if (newInput->getOutputType() == TXFMBase::BYTE_STREAM) {
//throw XSECException(XSECException::TransformInputOutputFail, "C14n canonicalisation transform requires DOM_NODES input");
// Need to parse into DOM_NODES
TXFMParser * parser;
XSECnew(parser, TXFMParser(mp_expansionDoc));
try{
parser->setInput(newInput);
}
catch (...) {
delete parser;
input = newInput;
throw;
}
input = parser;
parser->expandNameSpaces();
}
else
input = newInput;
// Set up for the new document
document = input->getDocument();
// Expand if necessary
this->expandNameSpaces();
keepComments = input->getCommentsStatus();
}
bool separator(unsigned char c) {
if (c >= 'a' && c <= 'z')
return false;
if (c >= 'A' && c <= 'Z')
return false;
return true;
}
XalanNode * findHereNodeFromXalan(XercesWrapperNavigator * xwn, XalanNode * n, DOMNode *h) {
const DOMNode * m = xwn->mapNode(n);
const XalanNode * ret;
if (m == h)
return n;
// Not this one - check the children
XalanNode * c = n->getFirstChild();
while (c != 0) {
ret = findHereNodeFromXalan(xwn, c, h);
if (ret != 0)
return (XalanNode *) ret;
c = c->getNextSibling();
}
return 0;
}
void TXFMXPath::evaluateExpr(DOMNode *h, safeBuffer inexpr) {
// Temporarily add any necessary name spaces into the document
XSECXPathNodeList addedNodes;
setXPathNS(document, XPathAtts, addedNodes, formatter, mp_nse);
XPathProcessorImpl xppi; // The processor
XercesParserLiaison xpl;
XercesDOMSupport xds(xpl);
XPathEvaluator xpe;
XPathFactoryDefault xpf;
XPathConstructionContextDefault xpcc;
XalanDocument * xd;
XalanNode * contextNode;
// Xalan can throw exceptions in all functions, so do one broad catch point.
try {
// Map to Xalan
xd = xpl.createDocument(document);
// For performing mapping
XercesDocumentWrapper *xdw = xpl.mapDocumentToWrapper(xd);
XercesWrapperNavigator xwn(xdw);
// Map the "here" node - but only if part of current document
XalanNode * hereNode = NULL;
if (h->getOwnerDocument() == document) {
hereNode = xwn.mapNode(h);
if (hereNode == NULL) {
hereNode = findHereNodeFromXalan(&xwn, xd, h);
if (hereNode == NULL) {
throw XSECException(XSECException::XPathError,
"Unable to find here node in Xalan Wrapper map");
}
}
}
// Now work out what we have to set up in the new processing
TXFMBase::nodeType inputType = input->getNodeType();
XalanDOMString cd; // For the moment assume the root is the context
const XalanDOMChar * cexpr;
safeBuffer contextExpr;
switch (inputType) {
case DOM_NODE_DOCUMENT :
case DOM_NODE_XPATH_NODESET :
// do XPath over the whole document and, if the input was an
// XPath Nodeset, then later intersect the result with the input nodelist
cd = XalanDOMString("/"); // Root node
cexpr = cd.c_str();
// The context node is the "root" node
contextNode =
xpe.selectSingleNode(
xds,
xd,
cexpr,
xd->getDocumentElement());
break;
case DOM_NODE_DOCUMENT_FRAGMENT :
{
// Need to map the DOM_Node that we are given from the input to the appropriate XalanNode
// Create the XPath expression to find the node
if (input->getFragmentId() != NULL) {
contextExpr.sbTranscodeIn("//descendant-or-self::node()[attribute::Id='");
contextExpr.sbXMLChCat(input->getFragmentId());
contextExpr.sbXMLChCat("']");
// Map the node
contextNode =
xpe.selectSingleNode(
xds,
xd,
contextExpr.rawXMLChBuffer(), //XalanDOMString((char *) contextExpr.rawBuffer()).c_str(),
xd->getDocumentElement());
if (contextNode == NULL) {
// Last Ditch
contextNode = xwn.mapNode(input->getFragmentNode());
}
}
else
contextNode = xwn.mapNode(input->getFragmentNode());
if (contextNode == NULL) {
// Something wrong
throw XSECException(XSECException::XPathError, "Error mapping context node");
}
break;
}
default :
throw XSECException(XSECException::XPathError); // Should never get here
}
safeBuffer str;
XPathEnvSupportDefault xpesd;
XObjectFactoryDefault xof;
XPathExecutionContextDefault xpec(xpesd, xds, xof);
ElementPrefixResolverProxy pr(xd->getDocumentElement(), xpesd, xds);
// Work around the fact that the XPath implementation is designed for XSLT, so does
// not allow here() as a NCName.
// THIS IS A KLUDGE AND SHOULD BE DONE BETTER
safeBuffer k(KLUDGE_PREFIX);
k.sbStrcatIn(":");
XMLSSize_t offset = inexpr.sbStrstr("here()");
while (offset >= 0) {
if (offset == 0 || offset == 1 ||
(!(inexpr[offset - 1] == ':' && inexpr[offset - 2] != ':') &&
separator(inexpr[offset - 1]))) {
inexpr.sbStrinsIn(k.rawCharBuffer(), offset);
}
offset = inexpr.sbOffsetStrstr("here()", offset + 11);
}
// Install the External function in the Environment handler
if (hereNode != NULL) {
xpesd.installExternalFunctionLocal(XalanDOMString(URI_ID_DSIG), XalanDOMString("here"), DSIGXPathHere(hereNode));
}
str.sbStrcpyIn("(descendant-or-self::node() | descendant-or-self::node()/attribute::* | descendant-or-self::node()/namespace::*)[");
str.sbStrcatIn(inexpr);
str.sbStrcatIn("]");
XPath * xp = xpf.create();
XalanDOMString Xexpr((char *) str.rawBuffer());
xppi.initXPath(*xp, xpcc, Xexpr, pr);
// Now resolve
XObjectPtr xObj = xp->execute(contextNode, pr, xpec);
// Now map to a list that others can use (naieve list at this time)
const NodeRefListBase& lst = xObj->nodeset();
int size = (int) lst.getLength();
const DOMNode *item;
for (int i = 0; i < size; ++ i) {
if (lst.item(i) == xd)
m_XPathMap.addNode(document);
else {
item = xwn.mapNode(lst.item(i));
m_XPathMap.addNode(item);
}
}
if (inputType == DOM_NODE_XPATH_NODESET) {
//the input list was a XPATH nodeset, so we must intersect the
// results of the XPath processing done above with the input nodeset
m_XPathMap.intersect(input->getXPathNodeList());
}
}
catch (const XSLException &e) {
safeBuffer msg;
// Whatever happens - fix any changes to the original document
clearXPathNS(document, addedNodes, formatter, mp_nse);
// Collate the exception message into an XSEC message.
msg.sbTranscodeIn("Xalan Exception : ");
msg.sbXMLChCat(e.getType());
msg.sbXMLChCat(" caught. Message : ");
msg.sbXMLChCat(e.getMessage().c_str());
throw XSECException(XSECException::XPathError,
msg.rawXMLChBuffer());
}
clearXPathNS(document, addedNodes, formatter, mp_nse);
}
void TXFMXPath::evaluateEnvelope(DOMNode *t) {
// A special case where the XPath expression is already known
if (document == NULL) {
throw XSECException(XSECException::XPathError,
"Attempt to define XPath Name Space before setInput called");
}
DOMElement * e = document->getDocumentElement();
if (e == NULL) {
throw XSECException(XSECException::XPathError,
"Element node not found in Document");
}
// Set the xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
e->setAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS, MAKE_UNICODE_STRING("xmlns:dsig"), DSIGConstants::s_unicodeStrURIDSIG);
// Evaluate
evaluateExpr(t, XPATH_EXPR_ENVELOPE);
// Now we are done, remove the namespace
e->removeAttributeNS(DSIGConstants::s_unicodeStrURIXMLNS, MAKE_UNICODE_STRING("dsig"));
}
// Methods to get tranform output type and input requirement
TXFMBase::ioType TXFMXPath::getInputType(void) const {
return TXFMBase::DOM_NODES;
}
TXFMBase::ioType TXFMXPath::getOutputType(void) const {
return TXFMBase::DOM_NODES;
}
TXFMBase::nodeType TXFMXPath::getNodeType(void) const {
return TXFMBase::DOM_NODE_XPATH_NODESET;
}
// Methods to get output data
unsigned int TXFMXPath::readBytes(XMLByte * const toFill, unsigned int maxToFill) {
return 0;
}
DOMDocument *TXFMXPath::getDocument() const {
return document;
}
#endif /* XSEC_HAVE_XPATH */