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