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("<"); 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(">"); 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("</"); 351 this.writer.write(currentElement.getTagName()); 352 this.writer.write(">"); 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;}</LI> 412 * <LI>all open angle brackets (<) with {@code &lt;}</LI> 413 * <LI>all quotation mark characters with {@code &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 &#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;"); 438 break; 439 440 case '<': 441 this.writer.write("&lt;"); 442 break; 443 444 case '"': 445 this.writer.write("&quot;"); 446 break; 447 448 case 0x09: // '\t' 449 this.writer.write("&#x9;"); 450 break; 451 452 case 0x0A: // '\n' 453 this.writer.write("&#xA;"); 454 break; 455 456 case 0x0D: // '\r' 457 this.writer.write("&#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("<?"); 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("&#xD;"); 493 break; 494 495 case ' ': 496 this.writer.write("·"); 497 break; 498 499 case '\n': 500 this.writer.write("¶\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("&#xD;"); 521 } else { 522 this.writer.write(c); 523 } 524 } 525 } 526 527 this.writer.write("?>"); 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("<!--"); 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("&#xD;"); 554 break; 555 556 case ' ': 557 this.writer.write("·"); 558 break; 559 560 case '\n': 561 this.writer.write("¶\n"); 562 break; 563 564 default: 565 this.writer.write(c); 566 break; 567 } 568 } 569 570 this.writer.write("-->"); 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;"); 593 break; 594 595 case '<': 596 this.writer.write("&lt;"); 597 break; 598 599 case '>': 600 this.writer.write("&gt;"); 601 break; 602 603 case 0xD: 604 this.writer.write("&#xD;"); 605 break; 606 607 case ' ': 608 this.writer.write("·"); 609 break; 610 611 case '\n': 612 this.writer.write("¶\n"); 613 break; 614 615 default: 616 this.writer.write(c); 617 break; 618 } 619 } 620 } 621 } 622