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