| /** |
| * 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.signature; |
| |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.util.Arrays; |
| import java.util.Set; |
| |
| import org.apache.xml.security.c14n.helper.AttrCompare; |
| import org.apache.xml.security.utils.XMLUtils; |
| import org.w3c.dom.Attr; |
| import org.w3c.dom.Comment; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NamedNodeMap; |
| import org.w3c.dom.Node; |
| import org.w3c.dom.ProcessingInstruction; |
| |
| /** |
| * Class XMLSignatureInputDebugger |
| */ |
| public class XMLSignatureInputDebugger { |
| |
| /** Field _xmlSignatureInput */ |
| private Set<Node> xpathNodeSet; |
| |
| private Set<String> inclusiveNamespaces; |
| |
| /** Field writer */ |
| private Writer writer; |
| |
| /** The HTML Prefix* */ |
| static final String HTMLPrefix = |
| "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n" |
| + "<html>\n" |
| + "<head>\n" |
| + "<title>Caninical XML node set</title>\n" |
| + "<style type=\"text/css\">\n" |
| + "<!-- \n" |
| + ".INCLUDED { \n" |
| + " color: #000000; \n" |
| + " background-color: \n" |
| + " #FFFFFF; \n" |
| + " font-weight: bold; } \n" |
| + ".EXCLUDED { \n" |
| + " color: #666666; \n" |
| + " background-color: \n" |
| + " #999999; } \n" |
| + ".INCLUDEDINCLUSIVENAMESPACE { \n" |
| + " color: #0000FF; \n" |
| + " background-color: #FFFFFF; \n" |
| + " font-weight: bold; \n" |
| + " font-style: italic; } \n" |
| + ".EXCLUDEDINCLUSIVENAMESPACE { \n" |
| + " color: #0000FF; \n" |
| + " background-color: #999999; \n" |
| + " font-style: italic; } \n" |
| + "--> \n" |
| + "</style> \n" |
| + "</head>\n" |
| + "<body bgcolor=\"#999999\">\n" |
| + "<h1>Explanation of the output</h1>\n" |
| + "<p>The following text contains the nodeset of the given Reference before it is canonicalized. There exist four different styles to indicate how a given node is treated.</p>\n" |
| + "<ul>\n" |
| + "<li class=\"INCLUDED\">A node which is in the node set is labeled using the INCLUDED style.</li>\n" |
| + "<li class=\"EXCLUDED\">A node which is <em>NOT</em> in the node set is labeled EXCLUDED style.</li>\n" |
| + "<li class=\"INCLUDEDINCLUSIVENAMESPACE\">A namespace which is in the node set AND in the InclusiveNamespaces PrefixList is labeled using the INCLUDEDINCLUSIVENAMESPACE style.</li>\n" |
| + "<li class=\"EXCLUDEDINCLUSIVENAMESPACE\">A namespace which is in NOT the node set AND in the InclusiveNamespaces PrefixList is labeled using the INCLUDEDINCLUSIVENAMESPACE style.</li>\n" |
| + "</ul>\n" + "<h1>Output</h1>\n" + "<pre>\n"; |
| |
| /** HTML Suffix * */ |
| static final String HTMLSuffix = "</pre></body></html>"; |
| |
| static final String HTMLExcludePrefix = "<span class=\"EXCLUDED\">"; |
| |
| static final String HTMLIncludePrefix = "<span class=\"INCLUDED\">"; |
| |
| static final String HTMLIncludeOrExcludeSuffix = "</span>"; |
| |
| static final String HTMLIncludedInclusiveNamespacePrefix = "<span class=\"INCLUDEDINCLUSIVENAMESPACE\">"; |
| |
| static final String HTMLExcludedInclusiveNamespacePrefix = "<span class=\"EXCLUDEDINCLUSIVENAMESPACE\">"; |
| |
| private static final int NODE_BEFORE_DOCUMENT_ELEMENT = -1; |
| |
| private static final int NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT = 0; |
| |
| private static final int NODE_AFTER_DOCUMENT_ELEMENT = 1; |
| |
| static final AttrCompare ATTR_COMPARE = new AttrCompare(); |
| |
| /** |
| * Constructor XMLSignatureInputDebugger |
| * |
| * @param xmlSignatureInput the signature to pretty print |
| */ |
| public XMLSignatureInputDebugger(XMLSignatureInput xmlSignatureInput) { |
| if (!xmlSignatureInput.isNodeSet()) { |
| this.xpathNodeSet = null; |
| } else { |
| this.xpathNodeSet = xmlSignatureInput.getInputNodeSet(); |
| } |
| } |
| |
| /** |
| * Constructor XMLSignatureInputDebugger |
| * |
| * @param xmlSignatureInput the signatur to pretty print |
| * @param inclusiveNamespace |
| */ |
| public XMLSignatureInputDebugger( |
| XMLSignatureInput xmlSignatureInput, |
| Set<String> inclusiveNamespace |
| ) { |
| this(xmlSignatureInput); |
| this.inclusiveNamespaces = inclusiveNamespace; |
| } |
| |
| /** |
| * Method getHTMLRepresentation |
| * |
| * @return The HTML Representation. |
| * @throws XMLSignatureException |
| */ |
| public String getHTMLRepresentation() throws XMLSignatureException { |
| if (this.xpathNodeSet == null || this.xpathNodeSet.isEmpty()) { |
| return HTMLPrefix + "<blink>no node set, sorry</blink>" + HTMLSuffix; |
| } |
| |
| // get only a single node as anchor to fetch the owner document |
| Node n = this.xpathNodeSet.iterator().next(); |
| |
| Document doc = XMLUtils.getOwnerDocument(n); |
| |
| try { |
| this.writer = new StringWriter(); |
| |
| this.canonicalizeXPathNodeSet(doc); |
| this.writer.close(); |
| |
| return this.writer.toString(); |
| } catch (IOException ex) { |
| throw new XMLSignatureException(ex); |
| } finally { |
| this.xpathNodeSet = null; |
| this.writer = null; |
| } |
| } |
| |
| /** |
| * Method canonicalizeXPathNodeSet |
| * |
| * @param currentNode |
| * @throws XMLSignatureException |
| * @throws IOException |
| */ |
| private void canonicalizeXPathNodeSet(Node currentNode) |
| throws XMLSignatureException, IOException { |
| |
| int currentNodeType = currentNode.getNodeType(); |
| switch (currentNodeType) { |
| |
| |
| case Node.ENTITY_NODE: |
| case Node.NOTATION_NODE: |
| case Node.DOCUMENT_FRAGMENT_NODE: |
| case Node.ATTRIBUTE_NODE: |
| throw new XMLSignatureException("empty", new Object[]{"An incorrect node was provided for c14n: " + currentNodeType}); |
| case Node.DOCUMENT_NODE: |
| this.writer.write(HTMLPrefix); |
| |
| for (Node currentChild = currentNode.getFirstChild(); |
| currentChild != null; currentChild = currentChild.getNextSibling()) { |
| this.canonicalizeXPathNodeSet(currentChild); |
| } |
| |
| this.writer.write(HTMLSuffix); |
| break; |
| |
| case Node.COMMENT_NODE: |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| int position = getPositionRelativeToDocumentElement(currentNode); |
| |
| if (position == NODE_AFTER_DOCUMENT_ELEMENT) { |
| this.writer.write("\n"); |
| } |
| |
| this.outputCommentToWriter((Comment) currentNode); |
| |
| if (position == NODE_BEFORE_DOCUMENT_ELEMENT) { |
| this.writer.write("\n"); |
| } |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| break; |
| |
| case Node.PROCESSING_INSTRUCTION_NODE: |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| position = getPositionRelativeToDocumentElement(currentNode); |
| |
| if (position == NODE_AFTER_DOCUMENT_ELEMENT) { |
| this.writer.write("\n"); |
| } |
| |
| this.outputPItoWriter((ProcessingInstruction) currentNode); |
| |
| if (position == NODE_BEFORE_DOCUMENT_ELEMENT) { |
| this.writer.write("\n"); |
| } |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| break; |
| |
| case Node.TEXT_NODE: |
| case Node.CDATA_SECTION_NODE: |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| outputTextToWriter(currentNode.getNodeValue()); |
| |
| for (Node nextSibling = currentNode.getNextSibling(); |
| nextSibling != null |
| && (nextSibling.getNodeType() == Node.TEXT_NODE |
| || nextSibling.getNodeType() == Node.CDATA_SECTION_NODE); |
| nextSibling = nextSibling.getNextSibling()) { |
| /* |
| * The XPath data model allows to select only the first of a |
| * sequence of mixed text and CDATA nodes. But we must output |
| * them all, so we must search: |
| * |
| * @see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6329 |
| */ |
| this.outputTextToWriter(nextSibling.getNodeValue()); |
| } |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| break; |
| |
| case Node.ELEMENT_NODE: |
| Element currentElement = (Element) currentNode; |
| |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| this.writer.write("<"); |
| this.writer.write(currentElement.getTagName()); |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| |
| // we output all Attrs which are available |
| NamedNodeMap attrs = currentElement.getAttributes(); |
| int attrsLength = attrs.getLength(); |
| Attr attrs2[] = new Attr[attrsLength]; |
| |
| for (int i = 0; i < attrsLength; i++) { |
| attrs2[i] = (Attr)attrs.item(i); |
| } |
| |
| Arrays.sort(attrs2, ATTR_COMPARE); |
| Object attrs3[] = attrs2; |
| |
| for (int i = 0; i < attrsLength; i++) { |
| Attr a = (Attr) attrs3[i]; |
| boolean included = this.xpathNodeSet.contains(a); |
| boolean inclusive = this.inclusiveNamespaces.contains(a.getName()); |
| |
| if (included) { |
| if (inclusive) { |
| // included and inclusive |
| this.writer.write(HTMLIncludedInclusiveNamespacePrefix); |
| } else { |
| // included and not inclusive |
| this.writer.write(HTMLIncludePrefix); |
| } |
| } else { |
| if (inclusive) { |
| // excluded and inclusive |
| this.writer.write(HTMLExcludedInclusiveNamespacePrefix); |
| } else { |
| // excluded and not inclusive |
| this.writer.write(HTMLExcludePrefix); |
| } |
| } |
| |
| this.outputAttrToWriter(a.getNodeName(), a.getNodeValue()); |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| } |
| |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| this.writer.write(">"); |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| |
| // traversal |
| for (Node currentChild = currentNode.getFirstChild(); |
| currentChild != null; |
| currentChild = currentChild.getNextSibling()) { |
| this.canonicalizeXPathNodeSet(currentChild); |
| } |
| |
| if (this.xpathNodeSet.contains(currentNode)) { |
| this.writer.write(HTMLIncludePrefix); |
| } else { |
| this.writer.write(HTMLExcludePrefix); |
| } |
| |
| this.writer.write("</"); |
| this.writer.write(currentElement.getTagName()); |
| this.writer.write(">"); |
| |
| this.writer.write(HTMLIncludeOrExcludeSuffix); |
| break; |
| |
| case Node.DOCUMENT_TYPE_NODE: |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Checks whether a Comment or ProcessingInstruction is before or after the |
| * document element. This is needed for prepending or appending "\n"s. |
| * |
| * @param currentNode |
| * comment or pi to check |
| * @return NODE_BEFORE_DOCUMENT_ELEMENT, |
| * NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT or |
| * NODE_AFTER_DOCUMENT_ELEMENT |
| * @see #NODE_BEFORE_DOCUMENT_ELEMENT |
| * @see #NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT |
| * @see #NODE_AFTER_DOCUMENT_ELEMENT |
| */ |
| private int getPositionRelativeToDocumentElement(Node currentNode) { |
| if (currentNode == null) { |
| return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; |
| } |
| |
| Document doc = currentNode.getOwnerDocument(); |
| |
| if (currentNode.getParentNode() != doc) { |
| return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; |
| } |
| |
| Element documentElement = doc.getDocumentElement(); |
| |
| if (documentElement == null) { |
| return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; |
| } |
| |
| if (documentElement == currentNode) { |
| return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; |
| } |
| |
| for (Node x = currentNode; x != null; x = x.getNextSibling()) { |
| if (x == documentElement) { |
| return NODE_BEFORE_DOCUMENT_ELEMENT; |
| } |
| } |
| |
| return NODE_AFTER_DOCUMENT_ELEMENT; |
| } |
| |
| /** |
| * Normalizes an {@link Attr}ibute value |
| * |
| * The string value of the node is modified by replacing |
| * <UL> |
| * <LI>all ampersands (&) with <CODE>&amp;</CODE></LI> |
| * <LI>all open angle brackets (<) with <CODE>&lt;</CODE></LI> |
| * <LI>all quotation mark characters with <CODE>&quot;</CODE></LI> |
| * <LI>and the whitespace characters <CODE>#x9</CODE>, #xA, and #xD, |
| * with character references. The character references are written in |
| * uppercase hexadecimal with no leading zeroes (for example, <CODE>#xD</CODE> |
| * is represented by the character reference <CODE>&#xD;</CODE>)</LI> |
| * </UL> |
| * |
| * @param name |
| * @param value |
| * @throws IOException |
| */ |
| private void outputAttrToWriter(String name, String value) throws IOException { |
| this.writer.write(" "); |
| this.writer.write(name); |
| this.writer.write("=\""); |
| |
| int length = value.length(); |
| |
| for (int i = 0; i < length; i++) { |
| char c = value.charAt(i); |
| |
| switch (c) { |
| |
| case '&': |
| this.writer.write("&amp;"); |
| break; |
| |
| case '<': |
| this.writer.write("&lt;"); |
| break; |
| |
| case '"': |
| this.writer.write("&quot;"); |
| break; |
| |
| case 0x09: // '\t' |
| this.writer.write("&#x9;"); |
| break; |
| |
| case 0x0A: // '\n' |
| this.writer.write("&#xA;"); |
| break; |
| |
| case 0x0D: // '\r' |
| this.writer.write("&#xD;"); |
| break; |
| |
| default: |
| this.writer.write(c); |
| break; |
| } |
| } |
| |
| this.writer.write("\""); |
| } |
| |
| /** |
| * Normalizes a {@link org.w3c.dom.Comment} value |
| * |
| * @param currentPI |
| * @throws IOException |
| */ |
| private void outputPItoWriter(ProcessingInstruction currentPI) throws IOException { |
| |
| if (currentPI == null) { |
| return; |
| } |
| |
| this.writer.write("<?"); |
| |
| String target = currentPI.getTarget(); |
| int length = target.length(); |
| |
| for (int i = 0; i < length; i++) { |
| char c = target.charAt(i); |
| |
| switch (c) { |
| |
| case 0x0D: |
| this.writer.write("&#xD;"); |
| break; |
| |
| case ' ': |
| this.writer.write("·"); |
| break; |
| |
| case '\n': |
| this.writer.write("¶\n"); |
| break; |
| |
| default: |
| this.writer.write(c); |
| break; |
| } |
| } |
| |
| String data = currentPI.getData(); |
| |
| length = data.length(); |
| |
| if (length > 0) { |
| this.writer.write(" "); |
| |
| for (int i = 0; i < length; i++) { |
| char c = data.charAt(i); |
| |
| switch (c) { |
| |
| case 0x0D: |
| this.writer.write("&#xD;"); |
| break; |
| |
| default: |
| this.writer.write(c); |
| break; |
| } |
| } |
| } |
| |
| this.writer.write("?>"); |
| } |
| |
| /** |
| * Method outputCommentToWriter |
| * |
| * @param currentComment |
| * @throws IOException |
| */ |
| private void outputCommentToWriter(Comment currentComment) throws IOException { |
| |
| if (currentComment == null) { |
| return; |
| } |
| |
| this.writer.write("<!--"); |
| |
| String data = currentComment.getData(); |
| int length = data.length(); |
| |
| for (int i = 0; i < length; i++) { |
| char c = data.charAt(i); |
| |
| switch (c) { |
| |
| case 0x0D: |
| this.writer.write("&#xD;"); |
| break; |
| |
| case ' ': |
| this.writer.write("·"); |
| break; |
| |
| case '\n': |
| this.writer.write("¶\n"); |
| break; |
| |
| default: |
| this.writer.write(c); |
| break; |
| } |
| } |
| |
| this.writer.write("-->"); |
| } |
| |
| /** |
| * Method outputTextToWriter |
| * |
| * @param text |
| * @throws IOException |
| */ |
| private void outputTextToWriter(String text) throws IOException { |
| if (text == null) { |
| return; |
| } |
| |
| int length = text.length(); |
| |
| for (int i = 0; i < length; i++) { |
| char c = text.charAt(i); |
| |
| switch (c) { |
| |
| case '&': |
| this.writer.write("&amp;"); |
| break; |
| |
| case '<': |
| this.writer.write("&lt;"); |
| break; |
| |
| case '>': |
| this.writer.write("&gt;"); |
| break; |
| |
| case 0xD: |
| this.writer.write("&#xD;"); |
| break; |
| |
| case ' ': |
| this.writer.write("·"); |
| break; |
| |
| case '\n': |
| this.writer.write("¶\n"); |
| break; |
| |
| default: |
| this.writer.write(c); |
| break; |
| } |
| } |
| } |
| } |