All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xml.security.c14n.implementations.Canonicalizer11 Maven / Gradle / Ivy

There is a newer version: 4.0.4
Show newest version
/**
 * 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.
 */
package org.apache.xml.security.c14n.implementations;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.helper.C14nHelper;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.utils.Constants;
import org.apache.xml.security.utils.XMLUtils;

/**
 * Implements 
 * Canonical XML Version 1.1, a W3C Proposed Recommendation from 29 
 * January 2008.
 *
 * @author Sean Mullan
 * @author Raul Benito
 */
public abstract class Canonicalizer11 extends CanonicalizerBase {
    
    private static final String XMLNS_URI = Constants.NamespaceSpecNS;
    private static final String XML_LANG_URI = Constants.XML_LANG_SPACE_SpecNS;
    private static org.apache.commons.logging.Log log = 
        org.apache.commons.logging.LogFactory.getLog(Canonicalizer11.class);
    private final SortedSet result = new TreeSet(COMPARE);
    
    private boolean firstCall = true;

    private static class XmlAttrStack {
        static class XmlsStackElement {
            int level;
            boolean rendered = false;
            List nodes = new ArrayList();
        };
        
        int currentLevel = 0;
        int lastlevel = 0;
        XmlsStackElement cur;
        List levels = new ArrayList();   
        
        void push(int level) {
            currentLevel = level;
            if (currentLevel == -1) {
                return;
            }
            cur = null;
            while (lastlevel >= currentLevel) {
                levels.remove(levels.size() - 1);
                int newSize = levels.size();
                if (newSize == 0) {
                    lastlevel = 0;
                    return;    				
                }
                lastlevel = (levels.get(newSize - 1)).level;
            }
        }
        
        void addXmlnsAttr(Attr n) {
            if (cur == null) {
                cur = new XmlsStackElement();
                cur.level = currentLevel;
                levels.add(cur);
                lastlevel = currentLevel;
            }
            cur.nodes.add(n);
        }
        
        void getXmlnsAttr(Collection col) {
            int size = levels.size() - 1;
            if (cur == null) {
                cur = new XmlsStackElement();
                cur.level = currentLevel;
                lastlevel = currentLevel;
                levels.add(cur);
            }
            boolean parentRendered = false;
            XmlsStackElement e = null;
            if (size == -1) {
                parentRendered = true;
            } else {
                e = levels.get(size);
                if (e.rendered && e.level + 1 == currentLevel) {
                    parentRendered = true;
                }
            }
            if (parentRendered) {
                col.addAll(cur.nodes);
                cur.rendered = true;
                return;
            }

            Map loa = new HashMap();    		
            List baseAttrs = new ArrayList();
            boolean successiveOmitted = true;
            for (; size >= 0; size--) {
                e = levels.get(size);
                if (e.rendered) {
                    successiveOmitted = false;
                }
                Iterator it = e.nodes.iterator();
                while (it.hasNext() && successiveOmitted) {
                    Attr n = it.next();
                    if (n.getLocalName().equals("base") && !e.rendered) {
                        baseAttrs.add(n);
                    } else if (!loa.containsKey(n.getName())) {
                        loa.put(n.getName(), n);
                    }
                }
            }
            if (!baseAttrs.isEmpty()) {
                Iterator it = col.iterator();
                String base = null;
                Attr baseAttr = null;
                while (it.hasNext()) {
                    Attr n = it.next();
                    if (n.getLocalName().equals("base")) {
                        base = n.getValue();
                        baseAttr = n;
                        break;
                    }
                }
                it = baseAttrs.iterator();
                while (it.hasNext()) {
                    Attr n = it.next();
                    if (base == null) {
                        base = n.getValue();
                        baseAttr = n;
                    } else {
                        try {
                            base = joinURI(n.getValue(), base);
                        } catch (URISyntaxException ue) {
                            if (log.isDebugEnabled()) {
                                log.debug(ue.getMessage(), ue);
                            }
                        }
                    }
                }
                if (base != null && base.length() != 0) {
                    baseAttr.setValue(base);
                    col.add(baseAttr);
                }
            }

            cur.rendered = true;
            col.addAll(loa.values());
        }
    };
    
    private XmlAttrStack xmlattrStack = new XmlAttrStack();
    
    /**
     * Constructor Canonicalizer11
     *
     * @param includeComments
     */
    public Canonicalizer11(boolean includeComments) {
        super(includeComments);
    }

    /**
     * Always throws a CanonicalizationException because this is inclusive c14n.
     *
     * @param xpathNodeSet
     * @param inclusiveNamespaces
     * @return none it always fails
     * @throws CanonicalizationException always
     */
    public byte[] engineCanonicalizeXPathNodeSet(
        Set xpathNodeSet, String inclusiveNamespaces
    ) throws CanonicalizationException {
        throw new CanonicalizationException("c14n.Canonicalizer.UnsupportedOperation");
    }

    /**
     * Always throws a CanonicalizationException because this is inclusive c14n.
     *
     * @param rootNode
     * @param inclusiveNamespaces
     * @return none it always fails
     * @throws CanonicalizationException
     */
    public byte[] engineCanonicalizeSubTree(
        Node rootNode, String inclusiveNamespaces
    ) throws CanonicalizationException {
        throw new CanonicalizationException("c14n.Canonicalizer.UnsupportedOperation");
    }
    
    /**
     * Returns the Attr[]s to be output for the given element.
     * 
* The code of this method is a copy of {@link #handleAttributes(Element, * NameSpaceSymbTable)}, * whereas it takes into account that subtree-c14n is -- well -- * subtree-based. * So if the element in question isRoot of c14n, it's parent is not in the * node set, as well as all other ancestors. * * @param element * @param ns * @return the Attr[]s to be output * @throws CanonicalizationException */ @Override protected Iterator handleAttributesSubtree(Element element, NameSpaceSymbTable ns) throws CanonicalizationException { if (!element.hasAttributes() && !firstCall) { return null; } // result will contain the attrs which have to be output final SortedSet result = this.result; result.clear(); if (element.hasAttributes()) { NamedNodeMap attrs = element.getAttributes(); int attrsLength = attrs.getLength(); for (int i = 0; i < attrsLength; i++) { Attr attribute = (Attr) attrs.item(i); String NUri = attribute.getNamespaceURI(); String NName = attribute.getLocalName(); String NValue = attribute.getValue(); if (!XMLNS_URI.equals(NUri)) { // It's not a namespace attr node. Add to the result and continue. result.add(attribute); } else if (!(XML.equals(NName) && XML_LANG_URI.equals(NValue))) { // The default mapping for xml must not be output. Node n = ns.addMappingAndRender(NName, NValue, attribute); if (n != null) { // Render the ns definition result.add((Attr)n); if (C14nHelper.namespaceIsRelative(attribute)) { Object exArgs[] = {element.getTagName(), NName, attribute.getNodeValue()}; throw new CanonicalizationException( "c14n.Canonicalizer.RelativeNamespace", exArgs ); } } } } } if (firstCall) { // It is the first node of the subtree // Obtain all the namespaces defined in the parents, and added to the output. ns.getUnrenderedNodes(result); // output the attributes in the xml namespace. xmlattrStack.getXmlnsAttr(result); firstCall = false; } return result.iterator(); } /** * Returns the Attr[]s to be output for the given element. *
* IMPORTANT: This method expects to work on a modified DOM tree, i.e. a * DOM which has been prepared using * {@link org.apache.xml.security.utils.XMLUtils#circumventBug2650( * org.w3c.dom.Document)}. * * @param element * @param ns * @return the Attr[]s to be output * @throws CanonicalizationException */ @Override protected Iterator handleAttributes(Element element, NameSpaceSymbTable ns) throws CanonicalizationException { // result will contain the attrs which have to be output xmlattrStack.push(ns.getLevel()); boolean isRealVisible = isVisibleDO(element, ns.getLevel()) == 1; final SortedSet result = this.result; result.clear(); if (element.hasAttributes()) { NamedNodeMap attrs = element.getAttributes(); int attrsLength = attrs.getLength(); for (int i = 0; i < attrsLength; i++) { Attr attribute = (Attr) attrs.item(i); String NUri = attribute.getNamespaceURI(); String NName = attribute.getLocalName(); String NValue = attribute.getValue(); if (!XMLNS_URI.equals(NUri)) { //A non namespace definition node. if (XML_LANG_URI.equals(NUri)) { if (NName.equals("id")) { if (isRealVisible) { // treat xml:id like any other attribute // (emit it, but don't inherit it) result.add(attribute); } } else { xmlattrStack.addXmlnsAttr(attribute); } } else if (isRealVisible) { //The node is visible add the attribute to the list of output attributes. result.add(attribute); } } else if (!XML.equals(NName) || !XML_LANG_URI.equals(NValue)) { /* except omit namespace node with local name xml, which defines * the xml prefix, if its string value is * http://www.w3.org/XML/1998/namespace. */ // add the prefix binding to the ns symb table. if (isVisible(attribute)) { if (isRealVisible || !ns.removeMappingIfRender(NName)) { // The xpath select this node output it if needed. Node n = ns.addMappingAndRender(NName, NValue, attribute); if (n != null) { result.add((Attr)n); if (C14nHelper.namespaceIsRelative(attribute)) { Object exArgs[] = { element.getTagName(), NName, attribute.getNodeValue() }; throw new CanonicalizationException( "c14n.Canonicalizer.RelativeNamespace", exArgs ); } } } } else { if (isRealVisible && !XMLNS.equals(NName)) { ns.removeMapping(NName); } else { ns.addMapping(NName, NValue, attribute); } } } } } if (isRealVisible) { //The element is visible, handle the xmlns definition Attr xmlns = element.getAttributeNodeNS(XMLNS_URI, XMLNS); Node n = null; if (xmlns == null) { //No xmlns def just get the already defined. n = ns.getMapping(XMLNS); } else if (!isVisible(xmlns)) { //There is a definition but the xmlns is not selected by the xpath. //then xmlns="" n = ns.addMappingAndRender( XMLNS, "", getNullNode(xmlns.getOwnerDocument())); } //output the xmlns def if needed. if (n != null) { result.add((Attr)n); } //Float all xml:* attributes of the unselected parent elements to this one. xmlattrStack.getXmlnsAttr(result); ns.getUnrenderedNodes(result); } return result.iterator(); } protected void circumventBugIfNeeded(XMLSignatureInput input) throws CanonicalizationException, ParserConfigurationException, IOException, SAXException { if (!input.isNeedsToBeExpanded()) { return; } Document doc = null; if (input.getSubNode() != null) { doc = XMLUtils.getOwnerDocument(input.getSubNode()); } else { doc = XMLUtils.getOwnerDocument(input.getNodeSet()); } XMLUtils.circumventBug2650(doc); } protected void handleParent(Element e, NameSpaceSymbTable ns) { if (!e.hasAttributes() && e.getNamespaceURI() == null) { return; } xmlattrStack.push(-1); NamedNodeMap attrs = e.getAttributes(); int attrsLength = attrs.getLength(); for (int i = 0; i < attrsLength; i++) { Attr attribute = (Attr) attrs.item(i); String NName = attribute.getLocalName(); String NValue = attribute.getNodeValue(); if (Constants.NamespaceSpecNS.equals(attribute.getNamespaceURI())) { if (!XML.equals(NName) || !Constants.XML_LANG_SPACE_SpecNS.equals(NValue)) { ns.addMapping(NName, NValue, attribute); } } else if (!"id".equals(NName) && XML_LANG_URI.equals(attribute.getNamespaceURI())) { xmlattrStack.addXmlnsAttr(attribute); } } if (e.getNamespaceURI() != null) { String NName = e.getPrefix(); String NValue = e.getNamespaceURI(); String Name; if (NName == null || NName.equals("")) { NName = "xmlns"; Name = "xmlns"; } else { Name = "xmlns:" + NName; } Attr n = e.getOwnerDocument().createAttributeNS("http://www.w3.org/2000/xmlns/", Name); n.setValue(NValue); ns.addMapping(NName, NValue, n); } } private static String joinURI(String baseURI, String relativeURI) throws URISyntaxException { String bscheme = null; String bauthority = null; String bpath = ""; String bquery = null; // pre-parse the baseURI if (baseURI != null) { if (baseURI.endsWith("..")) { baseURI = baseURI + "/"; } URI base = new URI(baseURI); bscheme = base.getScheme(); bauthority = base.getAuthority(); bpath = base.getPath(); bquery = base.getQuery(); } URI r = new URI(relativeURI); String rscheme = r.getScheme(); String rauthority = r.getAuthority(); String rpath = r.getPath(); String rquery = r.getQuery(); String tscheme, tauthority, tpath, tquery; if (rscheme != null && rscheme.equals(bscheme)) { rscheme = null; } if (rscheme != null) { tscheme = rscheme; tauthority = rauthority; tpath = removeDotSegments(rpath); tquery = rquery; } else { if (rauthority != null) { tauthority = rauthority; tpath = removeDotSegments(rpath); tquery = rquery; } else { if (rpath.length() == 0) { tpath = bpath; if (rquery != null) { tquery = rquery; } else { tquery = bquery; } } else { if (rpath.startsWith("/")) { tpath = removeDotSegments(rpath); } else { if (bauthority != null && bpath.length() == 0) { tpath = "/" + rpath; } else { int last = bpath.lastIndexOf('/'); if (last == -1) { tpath = rpath; } else { tpath = bpath.substring(0, last+1) + rpath; } } tpath = removeDotSegments(tpath); } tquery = rquery; } tauthority = bauthority; } tscheme = bscheme; } return new URI(tscheme, tauthority, tpath, tquery, null).toString(); } private static String removeDotSegments(String path) { if (log.isDebugEnabled()) { log.debug("STEP OUTPUT BUFFER\t\tINPUT BUFFER"); } // 1. The input buffer is initialized with the now-appended path // components then replace occurrences of "//" in the input buffer // with "/" until no more occurrences of "//" are in the input buffer. String input = path; while (input.indexOf("//") > -1) { input = input.replaceAll("//", "/"); } // Initialize the output buffer with the empty string. StringBuilder output = new StringBuilder(); // If the input buffer starts with a root slash "/" then move this // character to the output buffer. if (input.charAt(0) == '/') { output.append("/"); input = input.substring(1); } printStep("1 ", output.toString(), input); // While the input buffer is not empty, loop as follows while (input.length() != 0) { // 2A. If the input buffer begins with a prefix of "./", // then remove that prefix from the input buffer // else if the input buffer begins with a prefix of "../", then // if also the output does not contain the root slash "/" only, // then move this prefix to the end of the output buffer else // remove that prefix if (input.startsWith("./")) { input = input.substring(2); printStep("2A", output.toString(), input); } else if (input.startsWith("../")) { input = input.substring(3); if (!output.toString().equals("/")) { output.append("../"); } printStep("2A", output.toString(), input); // 2B. if the input buffer begins with a prefix of "/./" or "/.", // where "." is a complete path segment, then replace that prefix // with "/" in the input buffer; otherwise, } else if (input.startsWith("/./")) { input = input.substring(2); printStep("2B", output.toString(), input); } else if (input.equals("/.")) { // FIXME: what is complete path segment? input = input.replaceFirst("/.", "/"); printStep("2B", output.toString(), input); // 2C. if the input buffer begins with a prefix of "/../" or "/..", // where ".." is a complete path segment, then replace that prefix // with "/" in the input buffer and if also the output buffer is // empty, last segment in the output buffer equals "../" or "..", // where ".." is a complete path segment, then append ".." or "/.." // for the latter case respectively to the output buffer else // remove the last segment and its preceding "/" (if any) from the // output buffer and if hereby the first character in the output // buffer was removed and it was not the root slash then delete a // leading slash from the input buffer; otherwise, } else if (input.startsWith("/../")) { input = input.substring(3); if (output.length() == 0) { output.append("/"); } else if (output.toString().endsWith("../")) { output.append(".."); } else if (output.toString().endsWith("..")) { output.append("/.."); } else { int index = output.lastIndexOf("/"); if (index == -1) { output = new StringBuilder(); if (input.charAt(0) == '/') { input = input.substring(1); } } else { output = output.delete(index, output.length()); } } printStep("2C", output.toString(), input); } else if (input.equals("/..")) { // FIXME: what is complete path segment? input = input.replaceFirst("/..", "/"); if (output.length() == 0) { output.append("/"); } else if (output.toString().endsWith("../")) { output.append(".."); } else if (output.toString().endsWith("..")) { output.append("/.."); } else { int index = output.lastIndexOf("/"); if (index == -1) { output = new StringBuilder(); if (input.charAt(0) == '/') { input = input.substring(1); } } else { output = output.delete(index, output.length()); } } printStep("2C", output.toString(), input); // 2D. if the input buffer consists only of ".", then remove // that from the input buffer else if the input buffer consists // only of ".." and if the output buffer does not contain only // the root slash "/", then move the ".." to the output buffer // else delte it.; otherwise, } else if (input.equals(".")) { input = ""; printStep("2D", output.toString(), input); } else if (input.equals("..")) { if (!output.toString().equals("/")) { output.append(".."); } input = ""; printStep("2D", output.toString(), input); // 2E. move the first path segment (if any) in the input buffer // to the end of the output buffer, including the initial "/" // character (if any) and any subsequent characters up to, but not // including, the next "/" character or the end of the input buffer. } else { int end = -1; int begin = input.indexOf('/'); if (begin == 0) { end = input.indexOf('/', 1); } else { end = begin; begin = 0; } String segment; if (end == -1) { segment = input.substring(begin); input = ""; } else { segment = input.substring(begin, end); input = input.substring(end); } output.append(segment); printStep("2E", output.toString(), input); } } // 3. Finally, if the only or last segment of the output buffer is // "..", where ".." is a complete path segment not followed by a slash // then append a slash "/". The output buffer is returned as the result // of remove_dot_segments if (output.toString().endsWith("..")) { output.append("/"); printStep("3 ", output.toString(), input); } return output.toString(); } private static void printStep(String step, String output, String input) { if (log.isDebugEnabled()) { log.debug(" " + step + ": " + output); if (output.length() == 0) { log.debug("\t\t\t\t" + input); } else { log.debug("\t\t\t" + input); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy