| /* |
| * reserved comment block |
| * DO NOT REMOVE OR ALTER! |
| */ |
| /** |
| * 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 com.sun.org.apache.xml.internal.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 com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException; |
| import com.sun.org.apache.xml.internal.security.c14n.helper.C14nHelper; |
| import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput; |
| import com.sun.org.apache.xml.internal.security.utils.Constants; |
| import com.sun.org.apache.xml.internal.security.utils.XMLUtils; |
| |
| /** |
| * Implements <A HREF="http://www.w3.org/TR/2008/PR-xml-c14n11-20080129/"> |
| * Canonical XML Version 1.1</A>, 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 java.util.logging.Logger log = |
| java.util.logging.Logger.getLogger(Canonicalizer11.class.getName()); |
| private final SortedSet<Attr> result = new TreeSet<Attr>(COMPARE); |
| |
| private boolean firstCall = true; |
| |
| private static class XmlAttrStack { |
| static class XmlsStackElement { |
| int level; |
| boolean rendered = false; |
| List<Attr> nodes = new ArrayList<Attr>(); |
| }; |
| |
| int currentLevel = 0; |
| int lastlevel = 0; |
| XmlsStackElement cur; |
| List<XmlsStackElement> levels = new ArrayList<XmlsStackElement>(); |
| |
| 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<Attr> 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<String, Attr> loa = new HashMap<String, Attr>(); |
| List<Attr> baseAttrs = new ArrayList<Attr>(); |
| boolean successiveOmitted = true; |
| for (; size >= 0; size--) { |
| e = levels.get(size); |
| if (e.rendered) { |
| successiveOmitted = false; |
| } |
| Iterator<Attr> 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<Attr> 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.isLoggable(java.util.logging.Level.FINE)) { |
| log.log(java.util.logging.Level.FINE, 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<Node> 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. |
| * <br> |
| * 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<Attr> 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<Attr> 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. |
| * <br> |
| * IMPORTANT: This method expects to work on a modified DOM tree, i.e. a |
| * DOM which has been prepared using |
| * {@link com.sun.org.apache.xml.internal.security.utils.XMLUtils#circumventBug2650( |
| * org.w3c.dom.Document)}. |
| * |
| * @param element |
| * @param ns |
| * @return the Attr[]s to be output |
| * @throws CanonicalizationException |
| */ |
| @Override |
| protected Iterator<Attr> 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<Attr> 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.isLoggable(java.util.logging.Level.FINE)) { |
| log.log(java.util.logging.Level.FINE, "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.isLoggable(java.util.logging.Level.FINE)) { |
| log.log(java.util.logging.Level.FINE, " " + step + ": " + output); |
| if (output.length() == 0) { |
| log.log(java.util.logging.Level.FINE, "\t\t\t\t" + input); |
| } else { |
| log.log(java.util.logging.Level.FINE, "\t\t\t" + input); |
| } |
| } |
| } |
| } |