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.c14n.implementations; 24 25 import java.io.ByteArrayOutputStream; 26 import java.io.IOException; 27 import java.io.OutputStream; 28 import java.io.UnsupportedEncodingException; 29 import java.util.ArrayList; 30 import java.util.HashMap; 31 import java.util.Iterator; 32 import java.util.List; 33 import java.util.ListIterator; 34 import java.util.Map; 35 import java.util.Set; 36 37 import javax.xml.parsers.DocumentBuilder; 38 import javax.xml.parsers.DocumentBuilderFactory; 39 import javax.xml.parsers.ParserConfigurationException; 40 41 import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException; 42 import com.sun.org.apache.xml.internal.security.c14n.CanonicalizerSpi; 43 import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare; 44 import com.sun.org.apache.xml.internal.security.signature.NodeFilter; 45 import com.sun.org.apache.xml.internal.security.signature.XMLSignatureInput; 46 import com.sun.org.apache.xml.internal.security.utils.Constants; 47 import com.sun.org.apache.xml.internal.security.utils.UnsyncByteArrayOutputStream; 48 import com.sun.org.apache.xml.internal.security.utils.XMLUtils; 49 import org.w3c.dom.Attr; 50 import org.w3c.dom.Comment; 51 import org.w3c.dom.Element; 52 import org.w3c.dom.NamedNodeMap; 53 import org.w3c.dom.Node; 54 import org.w3c.dom.ProcessingInstruction; 55 import org.xml.sax.SAXException; 56 57 /** 58 * Abstract base class for canonicalization algorithms. 59 * 60 * @author Christian Geuer-Pollmann <geuerp@apache.org> 61 */ 62 public abstract class CanonicalizerBase extends CanonicalizerSpi { 63 public static final String XML = "xml"; 64 public static final String XMLNS = "xmlns"; 65 66 protected static final AttrCompare COMPARE = new AttrCompare(); 67 protected static final Attr nullNode; 68 69 private static final byte[] END_PI = {'?','>'}; 70 private static final byte[] BEGIN_PI = {'<','?'}; 71 private static final byte[] END_COMM = {'-','-','>'}; 72 private static final byte[] BEGIN_COMM = {'<','!','-','-'}; 73 private static final byte[] XA = {'&','#','x','A',';'}; 74 private static final byte[] X9 = {'&','#','x','9',';'}; 75 private static final byte[] QUOT = {'&','q','u','o','t',';'}; 76 private static final byte[] XD = {'&','#','x','D',';'}; 77 private static final byte[] GT = {'&','g','t',';'}; 78 private static final byte[] LT = {'&','l','t',';'}; 79 private static final byte[] END_TAG = {'<','/'}; 80 private static final byte[] AMP = {'&','a','m','p',';'}; 81 private static final byte[] equalsStr = {'=','\"'}; 82 83 protected static final int NODE_BEFORE_DOCUMENT_ELEMENT = -1; 84 protected static final int NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT = 0; 85 protected static final int NODE_AFTER_DOCUMENT_ELEMENT = 1; 86 87 static { 88 // The null xmlns definition. 89 try { 90 DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); 91 nullNode = documentBuilder.newDocument().createAttributeNS(Constants.NamespaceSpecNS, XMLNS); 92 nullNode.setValue(""); 93 } catch (Exception e) { 94 throw new RuntimeException("Unable to create nullNode: " + e); 95 } 96 } 97 98 private List<NodeFilter> nodeFilter; 99 100 private boolean includeComments; 101 private Set<Node> xpathNodeSet; 102 /** 103 * The node to be skipped/excluded from the DOM tree 104 * in subtree canonicalizations. 105 */ 106 private Node excludeNode; 107 private OutputStream writer = new ByteArrayOutputStream(); 108 109 /** 110 * Constructor CanonicalizerBase 111 * 112 * @param includeComments 113 */ 114 public CanonicalizerBase(boolean includeComments) { 115 this.includeComments = includeComments; 116 } 117 118 /** 119 * Method engineCanonicalizeSubTree 120 * @inheritDoc 121 * @param rootNode 122 * @throws CanonicalizationException 123 */ 124 public byte[] engineCanonicalizeSubTree(Node rootNode) 125 throws CanonicalizationException { 126 return engineCanonicalizeSubTree(rootNode, (Node)null); 127 } 128 129 /** 130 * Method engineCanonicalizeXPathNodeSet 131 * @inheritDoc 132 * @param xpathNodeSet 133 * @throws CanonicalizationException 134 */ 135 public byte[] engineCanonicalizeXPathNodeSet(Set<Node> xpathNodeSet) 136 throws CanonicalizationException { 137 this.xpathNodeSet = xpathNodeSet; 138 return engineCanonicalizeXPathNodeSetInternal(XMLUtils.getOwnerDocument(this.xpathNodeSet)); 139 } 140 141 /** 142 * Canonicalizes a Subtree node. 143 * @param input the root of the subtree to canicalize 144 * @return The canonicalize stream. 145 * @throws CanonicalizationException 146 */ 147 public byte[] engineCanonicalize(XMLSignatureInput input) throws CanonicalizationException { 148 try { 149 if (input.isExcludeComments()) { 150 includeComments = false; 151 } 152 if (input.isOctetStream()) { 153 return engineCanonicalize(input.getBytes()); 154 } 155 if (input.isElement()) { 156 return engineCanonicalizeSubTree(input.getSubNode(), input.getExcludeNode()); 157 } else if (input.isNodeSet()) { 158 nodeFilter = input.getNodeFilters(); 159 160 circumventBugIfNeeded(input); 161 162 if (input.getSubNode() != null) { 163 return engineCanonicalizeXPathNodeSetInternal(input.getSubNode()); 164 } else { 165 return engineCanonicalizeXPathNodeSet(input.getNodeSet()); 166 } 167 } 168 return null; 169 } catch (CanonicalizationException ex) { 170 throw new CanonicalizationException("empty", ex); 171 } catch (ParserConfigurationException ex) { 172 throw new CanonicalizationException("empty", ex); 173 } catch (IOException ex) { 174 throw new CanonicalizationException("empty", ex); 175 } catch (SAXException ex) { 176 throw new CanonicalizationException("empty", ex); 177 } 178 } 179 180 /** 181 * @param writer The writer to set. 182 */ 183 public void setWriter(OutputStream writer) { 184 this.writer = writer; 185 } 186 187 /** 188 * Canonicalizes a Subtree node. 189 * 190 * @param rootNode 191 * the root of the subtree to canonicalize 192 * @param excludeNode 193 * a node to be excluded from the canonicalize operation 194 * @return The canonicalize stream. 195 * @throws CanonicalizationException 196 */ 197 protected byte[] engineCanonicalizeSubTree(Node rootNode, Node excludeNode) 198 throws CanonicalizationException { 199 this.excludeNode = excludeNode; 200 try { 201 NameSpaceSymbTable ns = new NameSpaceSymbTable(); 202 int nodeLevel = NODE_BEFORE_DOCUMENT_ELEMENT; 203 if (rootNode != null && Node.ELEMENT_NODE == rootNode.getNodeType()) { 204 //Fills the nssymbtable with the definitions of the parent of the root subnode 205 getParentNameSpaces((Element)rootNode, ns); 206 nodeLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; 207 } 208 this.canonicalizeSubTree(rootNode, ns, rootNode, nodeLevel); 209 this.writer.flush(); 210 if (this.writer instanceof ByteArrayOutputStream) { 211 byte[] result = ((ByteArrayOutputStream)this.writer).toByteArray(); 212 if (reset) { 213 ((ByteArrayOutputStream)this.writer).reset(); 214 } else { 215 this.writer.close(); 216 } 217 return result; 218 } else if (this.writer instanceof UnsyncByteArrayOutputStream) { 219 byte[] result = ((UnsyncByteArrayOutputStream)this.writer).toByteArray(); 220 if (reset) { 221 ((UnsyncByteArrayOutputStream)this.writer).reset(); 222 } else { 223 this.writer.close(); 224 } 225 return result; 226 } else { 227 this.writer.close(); 228 } 229 return null; 230 231 } catch (UnsupportedEncodingException ex) { 232 throw new CanonicalizationException("empty", ex); 233 } catch (IOException ex) { 234 throw new CanonicalizationException("empty", ex); 235 } 236 } 237 238 239 /** 240 * Method canonicalizeSubTree, this function is a recursive one. 241 * 242 * @param currentNode 243 * @param ns 244 * @param endnode 245 * @throws CanonicalizationException 246 * @throws IOException 247 */ 248 protected final void canonicalizeSubTree( 249 Node currentNode, NameSpaceSymbTable ns, Node endnode, int documentLevel 250 ) throws CanonicalizationException, IOException { 251 if (isVisibleInt(currentNode) == -1) { 252 return; 253 } 254 Node sibling = null; 255 Node parentNode = null; 256 final OutputStream writer = this.writer; 257 final Node excludeNode = this.excludeNode; 258 final boolean includeComments = this.includeComments; 259 Map<String, byte[]> cache = new HashMap<String, byte[]>(); 260 do { 261 switch (currentNode.getNodeType()) { 262 263 case Node.ENTITY_NODE : 264 case Node.NOTATION_NODE : 265 case Node.ATTRIBUTE_NODE : 266 // illegal node type during traversal 267 throw new CanonicalizationException("empty"); 268 269 case Node.DOCUMENT_FRAGMENT_NODE : 270 case Node.DOCUMENT_NODE : 271 ns.outputNodePush(); 272 sibling = currentNode.getFirstChild(); 273 break; 274 275 case Node.COMMENT_NODE : 276 if (includeComments) { 277 outputCommentToWriter((Comment) currentNode, writer, documentLevel); 278 } 279 break; 280 281 case Node.PROCESSING_INSTRUCTION_NODE : 282 outputPItoWriter((ProcessingInstruction) currentNode, writer, documentLevel); 283 break; 284 285 case Node.TEXT_NODE : 286 case Node.CDATA_SECTION_NODE : 287 outputTextToWriter(currentNode.getNodeValue(), writer); 288 break; 289 290 case Node.ELEMENT_NODE : 291 documentLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; 292 if (currentNode == excludeNode) { 293 break; 294 } 295 Element currentElement = (Element)currentNode; 296 //Add a level to the nssymbtable. So latter can be pop-back. 297 ns.outputNodePush(); 298 writer.write('<'); 299 String name = currentElement.getTagName(); 300 UtfHelpper.writeByte(name, writer, cache); 301 302 Iterator<Attr> attrs = this.handleAttributesSubtree(currentElement, ns); 303 if (attrs != null) { 304 //we output all Attrs which are available 305 while (attrs.hasNext()) { 306 Attr attr = attrs.next(); 307 outputAttrToWriter(attr.getNodeName(), attr.getNodeValue(), writer, cache); 308 } 309 } 310 writer.write('>'); 311 sibling = currentNode.getFirstChild(); 312 if (sibling == null) { 313 writer.write(END_TAG); 314 UtfHelpper.writeStringToUtf8(name, writer); 315 writer.write('>'); 316 //We finished with this level, pop to the previous definitions. 317 ns.outputNodePop(); 318 if (parentNode != null) { 319 sibling = currentNode.getNextSibling(); 320 } 321 } else { 322 parentNode = currentElement; 323 } 324 break; 325 326 case Node.DOCUMENT_TYPE_NODE : 327 default : 328 break; 329 } 330 while (sibling == null && parentNode != null) { 331 writer.write(END_TAG); 332 UtfHelpper.writeByte(((Element)parentNode).getTagName(), writer, cache); 333 writer.write('>'); 334 //We finished with this level, pop to the previous definitions. 335 ns.outputNodePop(); 336 if (parentNode == endnode) { 337 return; 338 } 339 sibling = parentNode.getNextSibling(); 340 parentNode = parentNode.getParentNode(); 341 if (parentNode == null || Node.ELEMENT_NODE != parentNode.getNodeType()) { 342 documentLevel = NODE_AFTER_DOCUMENT_ELEMENT; 343 parentNode = null; 344 } 345 } 346 if (sibling == null) { 347 return; 348 } 349 currentNode = sibling; 350 sibling = currentNode.getNextSibling(); 351 } while(true); 352 } 353 354 355 private byte[] engineCanonicalizeXPathNodeSetInternal(Node doc) 356 throws CanonicalizationException { 357 try { 358 this.canonicalizeXPathNodeSet(doc, doc); 359 this.writer.flush(); 360 if (this.writer instanceof ByteArrayOutputStream) { 361 byte[] sol = ((ByteArrayOutputStream)this.writer).toByteArray(); 362 if (reset) { 363 ((ByteArrayOutputStream)this.writer).reset(); 364 } else { 365 this.writer.close(); 366 } 367 return sol; 368 } else if (this.writer instanceof UnsyncByteArrayOutputStream) { 369 byte[] result = ((UnsyncByteArrayOutputStream)this.writer).toByteArray(); 370 if (reset) { 371 ((UnsyncByteArrayOutputStream)this.writer).reset(); 372 } else { 373 this.writer.close(); 374 } 375 return result; 376 } else { 377 this.writer.close(); 378 } 379 return null; 380 } catch (UnsupportedEncodingException ex) { 381 throw new CanonicalizationException("empty", ex); 382 } catch (IOException ex) { 383 throw new CanonicalizationException("empty", ex); 384 } 385 } 386 387 /** 388 * Canonicalizes all the nodes included in the currentNode and contained in the 389 * xpathNodeSet field. 390 * 391 * @param currentNode 392 * @param endnode 393 * @throws CanonicalizationException 394 * @throws IOException 395 */ 396 protected final void canonicalizeXPathNodeSet(Node currentNode, Node endnode) 397 throws CanonicalizationException, IOException { 398 if (isVisibleInt(currentNode) == -1) { 399 return; 400 } 401 boolean currentNodeIsVisible = false; 402 NameSpaceSymbTable ns = new NameSpaceSymbTable(); 403 if (currentNode != null && Node.ELEMENT_NODE == currentNode.getNodeType()) { 404 getParentNameSpaces((Element)currentNode, ns); 405 } 406 if (currentNode == null) { 407 return; 408 } 409 Node sibling = null; 410 Node parentNode = null; 411 OutputStream writer = this.writer; 412 int documentLevel = NODE_BEFORE_DOCUMENT_ELEMENT; 413 Map<String, byte[]> cache = new HashMap<String, byte[]>(); 414 do { 415 switch (currentNode.getNodeType()) { 416 417 case Node.ENTITY_NODE : 418 case Node.NOTATION_NODE : 419 case Node.ATTRIBUTE_NODE : 420 // illegal node type during traversal 421 throw new CanonicalizationException("empty"); 422 423 case Node.DOCUMENT_FRAGMENT_NODE : 424 case Node.DOCUMENT_NODE : 425 ns.outputNodePush(); 426 sibling = currentNode.getFirstChild(); 427 break; 428 429 case Node.COMMENT_NODE : 430 if (this.includeComments && (isVisibleDO(currentNode, ns.getLevel()) == 1)) { 431 outputCommentToWriter((Comment) currentNode, writer, documentLevel); 432 } 433 break; 434 435 case Node.PROCESSING_INSTRUCTION_NODE : 436 if (isVisible(currentNode)) { 437 outputPItoWriter((ProcessingInstruction) currentNode, writer, documentLevel); 438 } 439 break; 440 441 case Node.TEXT_NODE : 442 case Node.CDATA_SECTION_NODE : 443 if (isVisible(currentNode)) { 444 outputTextToWriter(currentNode.getNodeValue(), writer); 445 for (Node nextSibling = currentNode.getNextSibling(); 446 (nextSibling != null) && ((nextSibling.getNodeType() == Node.TEXT_NODE) 447 || (nextSibling.getNodeType() == Node.CDATA_SECTION_NODE)); 448 nextSibling = nextSibling.getNextSibling()) { 449 outputTextToWriter(nextSibling.getNodeValue(), writer); 450 currentNode = nextSibling; 451 sibling = currentNode.getNextSibling(); 452 } 453 } 454 break; 455 456 case Node.ELEMENT_NODE : 457 documentLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT; 458 Element currentElement = (Element) currentNode; 459 //Add a level to the nssymbtable. So latter can be pop-back. 460 String name = null; 461 int i = isVisibleDO(currentNode, ns.getLevel()); 462 if (i == -1) { 463 sibling = currentNode.getNextSibling(); 464 break; 465 } 466 currentNodeIsVisible = (i == 1); 467 if (currentNodeIsVisible) { 468 ns.outputNodePush(); 469 writer.write('<'); 470 name = currentElement.getTagName(); 471 UtfHelpper.writeByte(name, writer, cache); 472 } else { 473 ns.push(); 474 } 475 476 Iterator<Attr> attrs = handleAttributes(currentElement,ns); 477 if (attrs != null) { 478 //we output all Attrs which are available 479 while (attrs.hasNext()) { 480 Attr attr = attrs.next(); 481 outputAttrToWriter(attr.getNodeName(), attr.getNodeValue(), writer, cache); 482 } 483 } 484 if (currentNodeIsVisible) { 485 writer.write('>'); 486 } 487 sibling = currentNode.getFirstChild(); 488 489 if (sibling == null) { 490 if (currentNodeIsVisible) { 491 writer.write(END_TAG); 492 UtfHelpper.writeByte(name, writer, cache); 493 writer.write('>'); 494 //We finished with this level, pop to the previous definitions. 495 ns.outputNodePop(); 496 } else { 497 ns.pop(); 498 } 499 if (parentNode != null) { 500 sibling = currentNode.getNextSibling(); 501 } 502 } else { 503 parentNode = currentElement; 504 } 505 break; 506 507 case Node.DOCUMENT_TYPE_NODE : 508 default : 509 break; 510 } 511 while (sibling == null && parentNode != null) { 512 if (isVisible(parentNode)) { 513 writer.write(END_TAG); 514 UtfHelpper.writeByte(((Element)parentNode).getTagName(), writer, cache); 515 writer.write('>'); 516 //We finished with this level, pop to the previous definitions. 517 ns.outputNodePop(); 518 } else { 519 ns.pop(); 520 } 521 if (parentNode == endnode) { 522 return; 523 } 524 sibling = parentNode.getNextSibling(); 525 parentNode = parentNode.getParentNode(); 526 if (parentNode == null || Node.ELEMENT_NODE != parentNode.getNodeType()) { 527 parentNode = null; 528 documentLevel = NODE_AFTER_DOCUMENT_ELEMENT; 529 } 530 } 531 if (sibling == null) { 532 return; 533 } 534 currentNode = sibling; 535 sibling = currentNode.getNextSibling(); 536 } while(true); 537 } 538 539 protected int isVisibleDO(Node currentNode, int level) { 540 if (nodeFilter != null) { 541 Iterator<NodeFilter> it = nodeFilter.iterator(); 542 while (it.hasNext()) { 543 int i = (it.next()).isNodeIncludeDO(currentNode, level); 544 if (i != 1) { 545 return i; 546 } 547 } 548 } 549 if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) { 550 return 0; 551 } 552 return 1; 553 } 554 555 protected int isVisibleInt(Node currentNode) { 556 if (nodeFilter != null) { 557 Iterator<NodeFilter> it = nodeFilter.iterator(); 558 while (it.hasNext()) { 559 int i = (it.next()).isNodeInclude(currentNode); 560 if (i != 1) { 561 return i; 562 } 563 } 564 } 565 if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) { 566 return 0; 567 } 568 return 1; 569 } 570 571 protected boolean isVisible(Node currentNode) { 572 if (nodeFilter != null) { 573 Iterator<NodeFilter> it = nodeFilter.iterator(); 574 while (it.hasNext()) { 575 if (it.next().isNodeInclude(currentNode) != 1) { 576 return false; 577 } 578 } 579 } 580 if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) { 581 return false; 582 } 583 return true; 584 } 585 586 protected void handleParent(Element e, NameSpaceSymbTable ns) { 587 if (!e.hasAttributes() && e.getNamespaceURI() == null) { 588 return; 589 } 590 NamedNodeMap attrs = e.getAttributes(); 591 int attrsLength = attrs.getLength(); 592 for (int i = 0; i < attrsLength; i++) { 593 Attr attribute = (Attr) attrs.item(i); 594 String NName = attribute.getLocalName(); 595 String NValue = attribute.getNodeValue(); 596 597 if (Constants.NamespaceSpecNS.equals(attribute.getNamespaceURI()) 598 && (!XML.equals(NName) || !Constants.XML_LANG_SPACE_SpecNS.equals(NValue))) { 599 ns.addMapping(NName, NValue, attribute); 600 } 601 } 602 if (e.getNamespaceURI() != null) { 603 String NName = e.getPrefix(); 604 String NValue = e.getNamespaceURI(); 605 String Name; 606 if (NName == null || NName.equals("")) { 607 NName = XMLNS; 608 Name = XMLNS; 609 } else { 610 Name = XMLNS + ":" + NName; 611 } 612 Attr n = e.getOwnerDocument().createAttributeNS("http://www.w3.org/2000/xmlns/", Name); 613 n.setValue(NValue); 614 ns.addMapping(NName, NValue, n); 615 } 616 } 617 618 /** 619 * Adds to ns the definitions from the parent elements of el 620 * @param el 621 * @param ns 622 */ 623 protected final void getParentNameSpaces(Element el, NameSpaceSymbTable ns) { 624 Node n1 = el.getParentNode(); 625 if (n1 == null || Node.ELEMENT_NODE != n1.getNodeType()) { 626 return; 627 } 628 //Obtain all the parents of the element 629 List<Element> parents = new ArrayList<Element>(); 630 Node parent = n1; 631 while (parent != null && Node.ELEMENT_NODE == parent.getNodeType()) { 632 parents.add((Element)parent); 633 parent = parent.getParentNode(); 634 } 635 //Visit them in reverse order. 636 ListIterator<Element> it = parents.listIterator(parents.size()); 637 while (it.hasPrevious()) { 638 Element ele = it.previous(); 639 handleParent(ele, ns); 640 } 641 parents.clear(); 642 Attr nsprefix; 643 if (((nsprefix = ns.getMappingWithoutRendered(XMLNS)) != null) 644 && "".equals(nsprefix.getValue())) { 645 ns.addMappingAndRender(XMLNS, "", nullNode); 646 } 647 } 648 649 /** 650 * Obtain the attributes to output for this node in XPathNodeSet c14n. 651 * 652 * @param element 653 * @param ns 654 * @return the attributes nodes to output. 655 * @throws CanonicalizationException 656 */ 657 abstract Iterator<Attr> handleAttributes(Element element, NameSpaceSymbTable ns) 658 throws CanonicalizationException; 659 660 /** 661 * Obtain the attributes to output for this node in a Subtree c14n. 662 * 663 * @param element 664 * @param ns 665 * @return the attributes nodes to output. 666 * @throws CanonicalizationException 667 */ 668 abstract Iterator<Attr> handleAttributesSubtree(Element element, NameSpaceSymbTable ns) 669 throws CanonicalizationException; 670 671 abstract void circumventBugIfNeeded(XMLSignatureInput input) 672 throws CanonicalizationException, ParserConfigurationException, IOException, SAXException; 673 674 /** 675 * Outputs an Attribute to the internal Writer. 676 * 677 * The string value of the node is modified by replacing 678 * <UL> 679 * <LI>all ampersands (&) with <CODE>&amp;</CODE></LI> 680 * <LI>all open angle brackets (<) with <CODE>&lt;</CODE></LI> 681 * <LI>all quotation mark characters with <CODE>&quot;</CODE></LI> 682 * <LI>and the whitespace characters <CODE>#x9</CODE>, #xA, and #xD, with character 683 * references. The character references are written in uppercase 684 * hexadecimal with no leading zeroes (for example, <CODE>#xD</CODE> is represented 685 * by the character reference <CODE>&#xD;</CODE>)</LI> 686 * </UL> 687 * 688 * @param name 689 * @param value 690 * @param writer 691 * @throws IOException 692 */ 693 protected static final void outputAttrToWriter( 694 final String name, final String value, 695 final OutputStream writer, final Map<String, byte[]> cache 696 ) throws IOException { 697 writer.write(' '); 698 UtfHelpper.writeByte(name, writer, cache); 699 writer.write(equalsStr); 700 byte[] toWrite; 701 final int length = value.length(); 702 int i = 0; 703 while (i < length) { 704 char c = value.charAt(i++); 705 706 switch (c) { 707 708 case '&' : 709 toWrite = AMP; 710 break; 711 712 case '<' : 713 toWrite = LT; 714 break; 715 716 case '"' : 717 toWrite = QUOT; 718 break; 719 720 case 0x09 : // '\t' 721 toWrite = X9; 722 break; 723 724 case 0x0A : // '\n' 725 toWrite = XA; 726 break; 727 728 case 0x0D : // '\r' 729 toWrite = XD; 730 break; 731 732 default : 733 if (c < 0x80) { 734 writer.write(c); 735 } else { 736 UtfHelpper.writeCharToUtf8(c, writer); 737 } 738 continue; 739 } 740 writer.write(toWrite); 741 } 742 743 writer.write('\"'); 744 } 745 746 /** 747 * Outputs a PI to the internal Writer. 748 * 749 * @param currentPI 750 * @param writer where to write the things 751 * @throws IOException 752 */ 753 protected void outputPItoWriter( 754 ProcessingInstruction currentPI, OutputStream writer, int position 755 ) throws IOException { 756 if (position == NODE_AFTER_DOCUMENT_ELEMENT) { 757 writer.write('\n'); 758 } 759 writer.write(BEGIN_PI); 760 761 final String target = currentPI.getTarget(); 762 int length = target.length(); 763 764 for (int i = 0; i < length; i++) { 765 char c = target.charAt(i); 766 if (c == 0x0D) { 767 writer.write(XD); 768 } else { 769 if (c < 0x80) { 770 writer.write(c); 771 } else { 772 UtfHelpper.writeCharToUtf8(c, writer); 773 } 774 } 775 } 776 777 final String data = currentPI.getData(); 778 779 length = data.length(); 780 781 if (length > 0) { 782 writer.write(' '); 783 784 for (int i = 0; i < length; i++) { 785 char c = data.charAt(i); 786 if (c == 0x0D) { 787 writer.write(XD); 788 } else { 789 UtfHelpper.writeCharToUtf8(c, writer); 790 } 791 } 792 } 793 794 writer.write(END_PI); 795 if (position == NODE_BEFORE_DOCUMENT_ELEMENT) { 796 writer.write('\n'); 797 } 798 } 799 800 /** 801 * Method outputCommentToWriter 802 * 803 * @param currentComment 804 * @param writer writer where to write the things 805 * @throws IOException 806 */ 807 protected void outputCommentToWriter( 808 Comment currentComment, OutputStream writer, int position 809 ) throws IOException { 810 if (position == NODE_AFTER_DOCUMENT_ELEMENT) { 811 writer.write('\n'); 812 } 813 writer.write(BEGIN_COMM); 814 815 final String data = currentComment.getData(); 816 final int length = data.length(); 817 818 for (int i = 0; i < length; i++) { 819 char c = data.charAt(i); 820 if (c == 0x0D) { 821 writer.write(XD); 822 } else { 823 if (c < 0x80) { 824 writer.write(c); 825 } else { 826 UtfHelpper.writeCharToUtf8(c, writer); 827 } 828 } 829 } 830 831 writer.write(END_COMM); 832 if (position == NODE_BEFORE_DOCUMENT_ELEMENT) { 833 writer.write('\n'); 834 } 835 } 836 837 /** 838 * Outputs a Text of CDATA section to the internal Writer. 839 * 840 * @param text 841 * @param writer writer where to write the things 842 * @throws IOException 843 */ 844 protected static final void outputTextToWriter( 845 final String text, final OutputStream writer 846 ) throws IOException { 847 final int length = text.length(); 848 byte[] toWrite; 849 for (int i = 0; i < length; i++) { 850 char c = text.charAt(i); 851 852 switch (c) { 853 854 case '&' : 855 toWrite = AMP; 856 break; 857 858 case '<' : 859 toWrite = LT; 860 break; 861 862 case '>' : 863 toWrite = GT; 864 break; 865 866 case 0xD : 867 toWrite = XD; 868 break; 869 870 default : 871 if (c < 0x80) { 872 writer.write(c); 873 } else { 874 UtfHelpper.writeCharToUtf8(c, writer); 875 } 876 continue; 877 } 878 writer.write(toWrite); 879 } 880 } 881 882 }