1 /*
2  * reserved comment block
3  * DO NOT REMOVE OR ALTER!
4  */
5 /**
6  * Licensed to the Apache Software Foundation (ASF) under one
7  * or more contributor license agreements. See the NOTICE file
8  * distributed with this work for additional information
9  * regarding copyright ownership. The ASF licenses this file
10  * to you under the Apache License, Version 2.0 (the
11  * "License"); you may not use this file except in compliance
12  * with the License. You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing,
17  * software distributed under the License is distributed on an
18  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19  * KIND, either express or implied. See the License for the
20  * specific language governing permissions and limitations
21  * under the License.
22  */
23 package com.sun.org.apache.xml.internal.security.signature;
24 
25 import java.io.IOException;
26 import java.io.StringWriter;
27 import java.io.Writer;
28 import java.util.Arrays;
29 import java.util.Set;
30 
31 import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
32 import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
33 import org.w3c.dom.Attr;
34 import org.w3c.dom.Comment;
35 import org.w3c.dom.Document;
36 import org.w3c.dom.Element;
37 import org.w3c.dom.NamedNodeMap;
38 import org.w3c.dom.Node;
39 import org.w3c.dom.ProcessingInstruction;
40 
41 /**
42  * Class XMLSignatureInputDebugger
43  */
44 public class XMLSignatureInputDebugger {
45 
46     /** Field _xmlSignatureInput */
47     private Set<Node> xpathNodeSet;
48 
49     private Set<String> inclusiveNamespaces;
50 
51     /** Field writer */
52     private Writer writer;
53 
54     /** The HTML Prefix* */
55     static final String HTMLPrefix =
56         "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n"
57         + "<html>\n"
58         + "<head>\n"
59         + "<title>Canonical XML node set</title>\n"
60         + "<style type=\"text/css\">\n"
61         + "<!-- \n"
62         + ".INCLUDED { \n"
63         + "   color: #000000; \n"
64         + "   background-color: \n"
65         + "   #FFFFFF; \n"
66         + "   font-weight: bold; } \n"
67         + ".EXCLUDED { \n"
68         + "   color: #666666; \n"
69         + "   background-color: \n"
70         + "   #999999; } \n"
71         + ".INCLUDEDINCLUSIVENAMESPACE { \n"
72         + "   color: #0000FF; \n"
73         + "   background-color: #FFFFFF; \n"
74         + "   font-weight: bold; \n"
75         + "   font-style: italic; } \n"
76         + ".EXCLUDEDINCLUSIVENAMESPACE { \n"
77         + "   color: #0000FF; \n"
78         + "   background-color: #999999; \n"
79         + "   font-style: italic; } \n"
80         + "--> \n"
81         + "</style> \n"
82         + "</head>\n"
83         + "<body bgcolor=\"#999999\">\n"
84         + "<h1>Explanation of the output</h1>\n"
85         + "<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"
86         + "<ul>\n"
87         + "<li class=\"INCLUDED\">A node which is in the node set is labeled using the INCLUDED style.</li>\n"
88         + "<li class=\"EXCLUDED\">A node which is <em>NOT</em> in the node set is labeled EXCLUDED style.</li>\n"
89         + "<li class=\"INCLUDEDINCLUSIVENAMESPACE\">A namespace which is in the node set AND in the InclusiveNamespaces PrefixList is labeled using the INCLUDEDINCLUSIVENAMESPACE style.</li>\n"
90         + "<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"
91         + "</ul>\n" + "<h1>Output</h1>\n" + "<pre>\n";
92 
93     /** HTML Suffix * */
94     static final String HTMLSuffix = "</pre></body></html>";
95 
96     static final String HTMLExcludePrefix = "<span class=\"EXCLUDED\">";
97 
98     static final String HTMLIncludePrefix = "<span class=\"INCLUDED\">";
99 
100     static final String HTMLIncludeOrExcludeSuffix = "</span>";
101 
102     static final String HTMLIncludedInclusiveNamespacePrefix = "<span class=\"INCLUDEDINCLUSIVENAMESPACE\">";
103 
104     static final String HTMLExcludedInclusiveNamespacePrefix = "<span class=\"EXCLUDEDINCLUSIVENAMESPACE\">";
105 
106     private static final int NODE_BEFORE_DOCUMENT_ELEMENT = -1;
107 
108     private static final int NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT = 0;
109 
110     private static final int NODE_AFTER_DOCUMENT_ELEMENT = 1;
111 
112     static final AttrCompare ATTR_COMPARE = new AttrCompare();
113 
114     /**
115      * Constructor XMLSignatureInputDebugger
116      *
117      * @param xmlSignatureInput the signature to pretty print
118      */
XMLSignatureInputDebugger(XMLSignatureInput xmlSignatureInput)119     public XMLSignatureInputDebugger(XMLSignatureInput xmlSignatureInput) {
120         if (!xmlSignatureInput.isNodeSet()) {
121             this.xpathNodeSet = null;
122         } else {
123             this.xpathNodeSet = xmlSignatureInput.getInputNodeSet();
124         }
125     }
126 
127     /**
128      * Constructor XMLSignatureInputDebugger
129      *
130      * @param xmlSignatureInput the signatur to pretty print
131      * @param inclusiveNamespace
132      */
XMLSignatureInputDebugger( XMLSignatureInput xmlSignatureInput, Set<String> inclusiveNamespace )133     public XMLSignatureInputDebugger(
134         XMLSignatureInput xmlSignatureInput,
135         Set<String> inclusiveNamespace
136     ) {
137         this(xmlSignatureInput);
138         this.inclusiveNamespaces = inclusiveNamespace;
139     }
140 
141     /**
142      * Method getHTMLRepresentation
143      *
144      * @return The HTML Representation.
145      * @throws XMLSignatureException
146      */
getHTMLRepresentation()147     public String getHTMLRepresentation() throws XMLSignatureException {
148         if (this.xpathNodeSet == null || this.xpathNodeSet.isEmpty()) {
149             return HTMLPrefix + "<blink>no node set, sorry</blink>" + HTMLSuffix;
150         }
151 
152         // get only a single node as anchor to fetch the owner document
153         Node n = this.xpathNodeSet.iterator().next();
154 
155         Document doc = XMLUtils.getOwnerDocument(n);
156 
157         try {
158             this.writer = new StringWriter();
159 
160             this.canonicalizeXPathNodeSet(doc);
161             this.writer.close();
162 
163             return this.writer.toString();
164         } catch (IOException ex) {
165             throw new XMLSignatureException(ex);
166         } finally {
167             this.xpathNodeSet = null;
168             this.writer = null;
169         }
170     }
171 
172     /**
173      * Method canonicalizeXPathNodeSet
174      *
175      * @param currentNode
176      * @throws XMLSignatureException
177      * @throws IOException
178      */
canonicalizeXPathNodeSet(Node currentNode)179     private void canonicalizeXPathNodeSet(Node currentNode)
180         throws XMLSignatureException, IOException {
181 
182         int currentNodeType = currentNode.getNodeType();
183         switch (currentNodeType) {
184 
185 
186         case Node.ENTITY_NODE:
187         case Node.NOTATION_NODE:
188         case Node.DOCUMENT_FRAGMENT_NODE:
189         case Node.ATTRIBUTE_NODE:
190             throw new XMLSignatureException("empty", new Object[]{"An incorrect node was provided for c14n: " + currentNodeType});
191         case Node.DOCUMENT_NODE:
192             this.writer.write(HTMLPrefix);
193 
194             for (Node currentChild = currentNode.getFirstChild();
195                 currentChild != null; currentChild = currentChild.getNextSibling()) {
196                 this.canonicalizeXPathNodeSet(currentChild);
197             }
198 
199             this.writer.write(HTMLSuffix);
200             break;
201 
202         case Node.COMMENT_NODE:
203             if (this.xpathNodeSet.contains(currentNode)) {
204                 this.writer.write(HTMLIncludePrefix);
205             } else {
206                 this.writer.write(HTMLExcludePrefix);
207             }
208 
209             int position = getPositionRelativeToDocumentElement(currentNode);
210 
211             if (position == NODE_AFTER_DOCUMENT_ELEMENT) {
212                 this.writer.write("\n");
213             }
214 
215             this.outputCommentToWriter((Comment) currentNode);
216 
217             if (position == NODE_BEFORE_DOCUMENT_ELEMENT) {
218                 this.writer.write("\n");
219             }
220 
221             this.writer.write(HTMLIncludeOrExcludeSuffix);
222             break;
223 
224         case Node.PROCESSING_INSTRUCTION_NODE:
225             if (this.xpathNodeSet.contains(currentNode)) {
226                 this.writer.write(HTMLIncludePrefix);
227             } else {
228                 this.writer.write(HTMLExcludePrefix);
229             }
230 
231             position = getPositionRelativeToDocumentElement(currentNode);
232 
233             if (position == NODE_AFTER_DOCUMENT_ELEMENT) {
234                 this.writer.write("\n");
235             }
236 
237             this.outputPItoWriter((ProcessingInstruction) currentNode);
238 
239             if (position == NODE_BEFORE_DOCUMENT_ELEMENT) {
240                 this.writer.write("\n");
241             }
242 
243             this.writer.write(HTMLIncludeOrExcludeSuffix);
244             break;
245 
246         case Node.TEXT_NODE:
247         case Node.CDATA_SECTION_NODE:
248             if (this.xpathNodeSet.contains(currentNode)) {
249                 this.writer.write(HTMLIncludePrefix);
250             } else {
251                 this.writer.write(HTMLExcludePrefix);
252             }
253 
254             outputTextToWriter(currentNode.getNodeValue());
255 
256             for (Node nextSibling = currentNode.getNextSibling();
257                 nextSibling != null
258                 && (nextSibling.getNodeType() == Node.TEXT_NODE
259                     || nextSibling.getNodeType() == Node.CDATA_SECTION_NODE);
260                 nextSibling = nextSibling.getNextSibling()) {
261                 /*
262                  * The XPath data model allows to select only the first of a
263                  * sequence of mixed text and CDATA nodes. But we must output
264                  * them all, so we must search:
265                  *
266                  * @see http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6329
267                  */
268                 this.outputTextToWriter(nextSibling.getNodeValue());
269             }
270 
271             this.writer.write(HTMLIncludeOrExcludeSuffix);
272             break;
273 
274         case Node.ELEMENT_NODE:
275             Element currentElement = (Element) currentNode;
276 
277             if (this.xpathNodeSet.contains(currentNode)) {
278                 this.writer.write(HTMLIncludePrefix);
279             } else {
280                 this.writer.write(HTMLExcludePrefix);
281             }
282 
283             this.writer.write("&lt;");
284             this.writer.write(currentElement.getTagName());
285 
286             this.writer.write(HTMLIncludeOrExcludeSuffix);
287 
288             // we output all Attrs which are available
289             NamedNodeMap attrs = currentElement.getAttributes();
290             int attrsLength = attrs.getLength();
291             Attr attrs2[] = new Attr[attrsLength];
292 
293             for (int i = 0; i < attrsLength; i++) {
294                 attrs2[i] = (Attr)attrs.item(i);
295             }
296 
297             Arrays.sort(attrs2, ATTR_COMPARE);
298             Object[] attrs3 = attrs2;
299 
300             for (int i = 0; i < attrsLength; i++) {
301                 Attr a = (Attr) attrs3[i];
302                 boolean included = this.xpathNodeSet.contains(a);
303                 boolean inclusive = this.inclusiveNamespaces.contains(a.getName());
304 
305                 if (included) {
306                     if (inclusive) {
307                         // included and inclusive
308                         this.writer.write(HTMLIncludedInclusiveNamespacePrefix);
309                     } else {
310                         // included and not inclusive
311                         this.writer.write(HTMLIncludePrefix);
312                     }
313                 } else {
314                     if (inclusive) {
315                         // excluded and inclusive
316                         this.writer.write(HTMLExcludedInclusiveNamespacePrefix);
317                     } else {
318                         // excluded and not inclusive
319                         this.writer.write(HTMLExcludePrefix);
320                     }
321                 }
322 
323                 this.outputAttrToWriter(a.getNodeName(), a.getNodeValue());
324                 this.writer.write(HTMLIncludeOrExcludeSuffix);
325             }
326 
327             if (this.xpathNodeSet.contains(currentNode)) {
328                 this.writer.write(HTMLIncludePrefix);
329             } else {
330                 this.writer.write(HTMLExcludePrefix);
331             }
332 
333             this.writer.write("&gt;");
334 
335             this.writer.write(HTMLIncludeOrExcludeSuffix);
336 
337             // traversal
338             for (Node currentChild = currentNode.getFirstChild();
339                 currentChild != null;
340                 currentChild = currentChild.getNextSibling()) {
341                 this.canonicalizeXPathNodeSet(currentChild);
342             }
343 
344             if (this.xpathNodeSet.contains(currentNode)) {
345                 this.writer.write(HTMLIncludePrefix);
346             } else {
347                 this.writer.write(HTMLExcludePrefix);
348             }
349 
350             this.writer.write("&lt;/");
351             this.writer.write(currentElement.getTagName());
352             this.writer.write("&gt;");
353 
354             this.writer.write(HTMLIncludeOrExcludeSuffix);
355             break;
356 
357         case Node.DOCUMENT_TYPE_NODE:
358         default:
359             break;
360         }
361     }
362 
363     /**
364      * Checks whether a Comment or ProcessingInstruction is before or after the
365      * document element. This is needed for prepending or appending "\n"s.
366      *
367      * @param currentNode
368      *            comment or pi to check
369      * @return NODE_BEFORE_DOCUMENT_ELEMENT,
370      *         NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT or
371      *         NODE_AFTER_DOCUMENT_ELEMENT
372      * @see #NODE_BEFORE_DOCUMENT_ELEMENT
373      * @see #NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT
374      * @see #NODE_AFTER_DOCUMENT_ELEMENT
375      */
getPositionRelativeToDocumentElement(Node currentNode)376     private int getPositionRelativeToDocumentElement(Node currentNode) {
377         if (currentNode == null) {
378             return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
379         }
380 
381         Document doc = currentNode.getOwnerDocument();
382 
383         if (currentNode.getParentNode() != doc) {
384             return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
385         }
386 
387         Element documentElement = doc.getDocumentElement();
388 
389         if (documentElement == null) {
390             return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
391         }
392 
393         if (documentElement == currentNode) {
394             return NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
395         }
396 
397         for (Node x = currentNode; x != null; x = x.getNextSibling()) {
398             if (x == documentElement) {
399                 return NODE_BEFORE_DOCUMENT_ELEMENT;
400             }
401         }
402 
403         return NODE_AFTER_DOCUMENT_ELEMENT;
404     }
405 
406     /**
407      * Normalizes an {@link Attr}ibute value
408      *
409      * The string value of the node is modified by replacing
410      * <UL>
411      * <LI>all ampersands (&) with {@code &amp;amp;}</LI>
412      * <LI>all open angle brackets (<) with {@code &amp;lt;}</LI>
413      * <LI>all quotation mark characters with {@code &amp;quot;}</LI>
414      * <LI>and the whitespace characters {@code #x9}, #xA, and #xD,
415      * with character references. The character references are written in
416      * uppercase hexadecimal with no leading zeroes (for example, {@code #xD}
417      * is represented by the character reference {@code &amp;#xD;})</LI>
418      * </UL>
419      *
420      * @param name
421      * @param value
422      * @throws IOException
423      */
outputAttrToWriter(String name, String value)424     private void outputAttrToWriter(String name, String value) throws IOException {
425         this.writer.write(" ");
426         this.writer.write(name);
427         this.writer.write("=\"");
428 
429         int length = value.length();
430 
431         for (int i = 0; i < length; i++) {
432             char c = value.charAt(i);
433 
434             switch (c) {
435 
436             case '&':
437                 this.writer.write("&amp;amp;");
438                 break;
439 
440             case '<':
441                 this.writer.write("&amp;lt;");
442                 break;
443 
444             case '"':
445                 this.writer.write("&amp;quot;");
446                 break;
447 
448             case 0x09: // '\t'
449                 this.writer.write("&amp;#x9;");
450                 break;
451 
452             case 0x0A: // '\n'
453                 this.writer.write("&amp;#xA;");
454                 break;
455 
456             case 0x0D: // '\r'
457                 this.writer.write("&amp;#xD;");
458                 break;
459 
460             default:
461                 this.writer.write(c);
462                 break;
463             }
464         }
465 
466         this.writer.write("\"");
467     }
468 
469     /**
470      * Normalizes a {@link org.w3c.dom.Comment} value
471      *
472      * @param currentPI
473      * @throws IOException
474      */
outputPItoWriter(ProcessingInstruction currentPI)475     private void outputPItoWriter(ProcessingInstruction currentPI) throws IOException {
476 
477         if (currentPI == null) {
478             return;
479         }
480 
481         this.writer.write("&lt;?");
482 
483         String target = currentPI.getTarget();
484         int length = target.length();
485 
486         for (int i = 0; i < length; i++) {
487             char c = target.charAt(i);
488 
489             switch (c) {
490 
491             case 0x0D:
492                 this.writer.write("&amp;#xD;");
493                 break;
494 
495             case ' ':
496                 this.writer.write("&middot;");
497                 break;
498 
499             case '\n':
500                 this.writer.write("&para;\n");
501                 break;
502 
503             default:
504                 this.writer.write(c);
505                 break;
506             }
507         }
508 
509         String data = currentPI.getData();
510 
511         length = data.length();
512 
513         if (length > 0) {
514             this.writer.write(" ");
515 
516             for (int i = 0; i < length; i++) {
517                 char c = data.charAt(i);
518 
519                 if (c == 0x0D) {
520                     this.writer.write("&amp;#xD;");
521                 } else {
522                     this.writer.write(c);
523                 }
524             }
525         }
526 
527         this.writer.write("?&gt;");
528     }
529 
530     /**
531      * Method outputCommentToWriter
532      *
533      * @param currentComment
534      * @throws IOException
535      */
outputCommentToWriter(Comment currentComment)536     private void outputCommentToWriter(Comment currentComment) throws IOException {
537 
538         if (currentComment == null) {
539             return;
540         }
541 
542         this.writer.write("&lt;!--");
543 
544         String data = currentComment.getData();
545         int length = data.length();
546 
547         for (int i = 0; i < length; i++) {
548             char c = data.charAt(i);
549 
550             switch (c) {
551 
552             case 0x0D:
553                 this.writer.write("&amp;#xD;");
554                 break;
555 
556             case ' ':
557                 this.writer.write("&middot;");
558                 break;
559 
560             case '\n':
561                 this.writer.write("&para;\n");
562                 break;
563 
564             default:
565                 this.writer.write(c);
566                 break;
567             }
568         }
569 
570         this.writer.write("--&gt;");
571     }
572 
573     /**
574      * Method outputTextToWriter
575      *
576      * @param text
577      * @throws IOException
578      */
outputTextToWriter(String text)579     private void outputTextToWriter(String text) throws IOException {
580         if (text == null) {
581             return;
582         }
583 
584         int length = text.length();
585 
586         for (int i = 0; i < length; i++) {
587             char c = text.charAt(i);
588 
589             switch (c) {
590 
591             case '&':
592                 this.writer.write("&amp;amp;");
593                 break;
594 
595             case '<':
596                 this.writer.write("&amp;lt;");
597                 break;
598 
599             case '>':
600                 this.writer.write("&amp;gt;");
601                 break;
602 
603             case 0xD:
604                 this.writer.write("&amp;#xD;");
605                 break;
606 
607             case ' ':
608                 this.writer.write("&middot;");
609                 break;
610 
611             case '\n':
612                 this.writer.write("&para;\n");
613                 break;
614 
615             default:
616                 this.writer.write(c);
617                 break;
618             }
619         }
620     }
621 }
622