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.DOMException;
50 import org.w3c.dom.Document;
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  * Please note that these implementations are NOT thread safe - please see the following JIRA for more information:
60  * https://issues.apache.org/jira/browse/SANTUARIO-463
61  */
62 public abstract class CanonicalizerBase extends CanonicalizerSpi {
63     public static final String XML = "xml";
64     public static final String XMLNS = "xmlns";
65     public static final String XMLNS_URI = Constants.NamespaceSpecNS;
66     public static final String XML_LANG_URI = Constants.XML_LANG_SPACE_SpecNS;
67 
68     protected static final AttrCompare COMPARE = new AttrCompare();
69 
70     // Make sure you clone the following mutable arrays before passing to
71     // potentially untrusted objects such as OutputStreams.
72     private static final byte[] END_PI = {'?','>'};
73     private static final byte[] BEGIN_PI = {'<','?'};
74     private static final byte[] END_COMM = {'-','-','>'};
75     private static final byte[] BEGIN_COMM = {'<','!','-','-'};
76     private static final byte[] XA = {'&','#','x','A',';'};
77     private static final byte[] X9 = {'&','#','x','9',';'};
78     private static final byte[] QUOT = {'&','q','u','o','t',';'};
79     private static final byte[] XD = {'&','#','x','D',';'};
80     private static final byte[] GT = {'&','g','t',';'};
81     private static final byte[] LT = {'&','l','t',';'};
82     private static final byte[] END_TAG = {'<','/'};
83     private static final byte[] AMP = {'&','a','m','p',';'};
84     private static final byte[] EQUALS_STR = {'=','\"'};
85 
86     protected static final int NODE_BEFORE_DOCUMENT_ELEMENT = -1;
87     protected static final int NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT = 0;
88     protected static final int NODE_AFTER_DOCUMENT_ELEMENT = 1;
89 
90     private List<NodeFilter> nodeFilter;
91 
92     private boolean includeComments;
93     private Set<Node> xpathNodeSet;
94 
95     /**
96      * The node to be skipped/excluded from the DOM tree
97      * in subtree canonicalizations.
98      */
99     private Node excludeNode;
100     private OutputStream writer = new ByteArrayOutputStream();
101 
102    /**
103     * The null xmlns definition.
104     */
105     private Attr nullNode;
106 
107     /**
108      * Constructor CanonicalizerBase
109      *
110      * @param includeComments
111      */
CanonicalizerBase(boolean includeComments)112     public CanonicalizerBase(boolean includeComments) {
113         this.includeComments = includeComments;
114     }
115 
116     /**
117      * Method engineCanonicalizeSubTree
118      * {@inheritDoc}
119      * @param rootNode
120      * @throws CanonicalizationException
121      */
engineCanonicalizeSubTree(Node rootNode)122     public byte[] engineCanonicalizeSubTree(Node rootNode)
123         throws CanonicalizationException {
124         return engineCanonicalizeSubTree(rootNode, (Node)null);
125     }
126 
127     /**
128      * Method engineCanonicalizeXPathNodeSet
129      * {@inheritDoc}
130      * @param xpathNodeSet
131      * @throws CanonicalizationException
132      */
engineCanonicalizeXPathNodeSet(Set<Node> xpathNodeSet)133     public byte[] engineCanonicalizeXPathNodeSet(Set<Node> xpathNodeSet)
134         throws CanonicalizationException {
135         this.xpathNodeSet = xpathNodeSet;
136         return engineCanonicalizeXPathNodeSetInternal(XMLUtils.getOwnerDocument(this.xpathNodeSet));
137     }
138 
139     /**
140      * Canonicalizes a Subtree node.
141      * @param input the root of the subtree to canicalize
142      * @return The canonicalize stream.
143      * @throws CanonicalizationException
144      */
engineCanonicalize(XMLSignatureInput input)145     public byte[] engineCanonicalize(XMLSignatureInput input) throws CanonicalizationException {
146         try {
147             if (input.isExcludeComments()) {
148                 includeComments = false;
149             }
150             if (input.isOctetStream()) {
151                 return engineCanonicalize(input.getBytes());
152             }
153             if (input.isElement()) {
154                 return engineCanonicalizeSubTree(input.getSubNode(), input.getExcludeNode());
155             } else if (input.isNodeSet()) {
156                 nodeFilter = input.getNodeFilters();
157 
158                 circumventBugIfNeeded(input);
159 
160                 if (input.getSubNode() != null) {
161                     return engineCanonicalizeXPathNodeSetInternal(input.getSubNode());
162                 } else {
163                     return engineCanonicalizeXPathNodeSet(input.getNodeSet());
164                 }
165             }
166             return null;
167         } catch (ParserConfigurationException ex) {
168             throw new CanonicalizationException(ex);
169         } catch (IOException ex) {
170             throw new CanonicalizationException(ex);
171         } catch (SAXException ex) {
172             throw new CanonicalizationException(ex);
173         }
174     }
175 
176     /**
177      * @param writer The writer to set.
178      */
setWriter(OutputStream writer)179     public void setWriter(OutputStream writer) {
180         this.writer = writer;
181     }
182 
getWriter()183     protected OutputStream getWriter() {
184         return 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      */
engineCanonicalizeSubTree(Node rootNode, Node excludeNode)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(ex);
233         } catch (IOException ex) {
234             throw new CanonicalizationException(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      */
canonicalizeSubTree( Node currentNode, NameSpaceSymbTable ns, Node endnode, int documentLevel )248     protected final void canonicalizeSubTree(
249         Node currentNode, NameSpaceSymbTable ns, Node endnode, int documentLevel
250     ) throws CanonicalizationException, IOException {
251         if (currentNode == null || 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<>();
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                                                     new Object[]{"illegal node type during traversal"});
269 
270             case Node.DOCUMENT_FRAGMENT_NODE :
271             case Node.DOCUMENT_NODE :
272                 ns.outputNodePush();
273                 sibling = currentNode.getFirstChild();
274                 break;
275 
276             case Node.COMMENT_NODE :
277                 if (includeComments) {
278                     outputCommentToWriter((Comment) currentNode, writer, documentLevel);
279                 }
280                 break;
281 
282             case Node.PROCESSING_INSTRUCTION_NODE :
283                 outputPItoWriter((ProcessingInstruction) currentNode, writer, documentLevel);
284                 break;
285 
286             case Node.TEXT_NODE :
287             case Node.CDATA_SECTION_NODE :
288                 outputTextToWriter(currentNode.getNodeValue(), writer);
289                 break;
290 
291             case Node.ELEMENT_NODE :
292                 documentLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
293                 if (currentNode == excludeNode) {
294                     break;
295                 }
296                 Element currentElement = (Element)currentNode;
297                 //Add a level to the nssymbtable. So latter can be pop-back.
298                 ns.outputNodePush();
299                 writer.write('<');
300                 String name = currentElement.getTagName();
301                 UtfHelpper.writeByte(name, writer, cache);
302 
303                 outputAttributesSubtree(currentElement, ns, cache);
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 
engineCanonicalizeXPathNodeSetInternal(Node doc)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(ex);
377         } catch (IOException ex) {
378             throw new CanonicalizationException(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      */
canonicalizeXPathNodeSet(Node currentNode, Node endnode)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         int documentLevel = NODE_BEFORE_DOCUMENT_ELEMENT;
407         Map<String, byte[]> cache = new HashMap<>();
408         do {
409             switch (currentNode.getNodeType()) {
410 
411             case Node.ENTITY_NODE :
412             case Node.NOTATION_NODE :
413             case Node.ATTRIBUTE_NODE :
414                 // illegal node type during traversal
415                 throw new CanonicalizationException("empty",
416                                                     new Object[]{"illegal node type during traversal"});
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                 outputAttributes(currentElement, ns, cache);
472 
473                 if (currentNodeIsVisible) {
474                     writer.write('>');
475                 }
476                 sibling = currentNode.getFirstChild();
477 
478                 if (sibling == null) {
479                     if (currentNodeIsVisible) {
480                         writer.write(END_TAG.clone());
481                         UtfHelpper.writeByte(name, writer, cache);
482                         writer.write('>');
483                         //We finished with this level, pop to the previous definitions.
484                         ns.outputNodePop();
485                     } else {
486                         ns.pop();
487                     }
488                     if (parentNode != null) {
489                         sibling = currentNode.getNextSibling();
490                     }
491                 } else {
492                     parentNode = currentElement;
493                 }
494                 break;
495 
496             case Node.DOCUMENT_TYPE_NODE :
497             default :
498                 break;
499             }
500             while (sibling == null && parentNode != null) {
501                 if (isVisible(parentNode)) {
502                     writer.write(END_TAG.clone());
503                     UtfHelpper.writeByte(((Element)parentNode).getTagName(), writer, cache);
504                     writer.write('>');
505                     //We finished with this level, pop to the previous definitions.
506                     ns.outputNodePop();
507                 } else {
508                     ns.pop();
509                 }
510                 if (parentNode == endnode) {
511                     return;
512                 }
513                 sibling = parentNode.getNextSibling();
514                 parentNode = parentNode.getParentNode();
515                 if (parentNode == null || Node.ELEMENT_NODE != parentNode.getNodeType()) {
516                     parentNode = null;
517                     documentLevel = NODE_AFTER_DOCUMENT_ELEMENT;
518                 }
519             }
520             if (sibling == null) {
521                 return;
522             }
523             currentNode = sibling;
524             sibling = currentNode.getNextSibling();
525         } while(true);
526     }
527 
isVisibleDO(Node currentNode, int level)528     protected int isVisibleDO(Node currentNode, int level) {
529         if (nodeFilter != null) {
530             Iterator<NodeFilter> it = nodeFilter.iterator();
531             while (it.hasNext()) {
532                 int i = it.next().isNodeIncludeDO(currentNode, level);
533                 if (i != 1) {
534                     return i;
535                 }
536             }
537         }
538         if (this.xpathNodeSet != null && !this.xpathNodeSet.contains(currentNode)) {
539             return 0;
540         }
541         return 1;
542     }
543 
isVisibleInt(Node currentNode)544     protected int isVisibleInt(Node currentNode) {
545         if (nodeFilter != null) {
546             Iterator<NodeFilter> it = nodeFilter.iterator();
547             while (it.hasNext()) {
548                 int i = it.next().isNodeInclude(currentNode);
549                 if (i != 1) {
550                     return i;
551                 }
552             }
553         }
554         if (this.xpathNodeSet != null && !this.xpathNodeSet.contains(currentNode)) {
555             return 0;
556         }
557         return 1;
558     }
559 
isVisible(Node currentNode)560     protected boolean isVisible(Node currentNode) {
561         if (nodeFilter != null) {
562             Iterator<NodeFilter> it = nodeFilter.iterator();
563             while (it.hasNext()) {
564                 if (it.next().isNodeInclude(currentNode) != 1) {
565                     return false;
566                 }
567             }
568         }
569         if (this.xpathNodeSet != null && !this.xpathNodeSet.contains(currentNode)) {
570             return false;
571         }
572         return true;
573     }
574 
handleParent(Element e, NameSpaceSymbTable ns)575     protected void handleParent(Element e, NameSpaceSymbTable ns) {
576         if (!e.hasAttributes() && e.getNamespaceURI() == null) {
577             return;
578         }
579         NamedNodeMap attrs = e.getAttributes();
580         int attrsLength = attrs.getLength();
581         for (int i = 0; i < attrsLength; i++) {
582             Attr attribute = (Attr) attrs.item(i);
583             String NName = attribute.getLocalName();
584             String NValue = attribute.getNodeValue();
585 
586             if (Constants.NamespaceSpecNS.equals(attribute.getNamespaceURI())
587                 && (!XML.equals(NName) || !Constants.XML_LANG_SPACE_SpecNS.equals(NValue))) {
588                 ns.addMapping(NName, NValue, attribute);
589             }
590         }
591         if (e.getNamespaceURI() != null) {
592             String NName = e.getPrefix();
593             String NValue = e.getNamespaceURI();
594             String Name;
595             if (NName == null || NName.equals("")) {
596                 NName = XMLNS;
597                 Name = XMLNS;
598             } else {
599                 Name = XMLNS + ":" + NName;
600             }
601             Attr n = e.getOwnerDocument().createAttributeNS("http://www.w3.org/2000/xmlns/", Name);
602             n.setValue(NValue);
603             ns.addMapping(NName, NValue, n);
604         }
605     }
606 
607     /**
608      * Adds to ns the definitions from the parent elements of el
609      * @param el
610      * @param ns
611      */
getParentNameSpaces(Element el, NameSpaceSymbTable ns)612     protected final void getParentNameSpaces(Element el, NameSpaceSymbTable ns)  {
613         Node n1 = el.getParentNode();
614         if (n1 == null || Node.ELEMENT_NODE != n1.getNodeType()) {
615             return;
616         }
617         //Obtain all the parents of the element
618         List<Element> parents = new ArrayList<>();
619         Node parent = n1;
620         while (parent != null && Node.ELEMENT_NODE == parent.getNodeType()) {
621             parents.add((Element)parent);
622             parent = parent.getParentNode();
623         }
624         //Visit them in reverse order.
625         ListIterator<Element> it = parents.listIterator(parents.size());
626         while (it.hasPrevious()) {
627             Element ele = it.previous();
628             handleParent(ele, ns);
629         }
630         parents.clear();
631         Attr nsprefix = ns.getMappingWithoutRendered(XMLNS);
632         if (nsprefix != null && "".equals(nsprefix.getValue())) {
633             ns.addMappingAndRender(
634                     XMLNS, "", getNullNode(nsprefix.getOwnerDocument()));
635         }
636     }
637 
638     /**
639      * Output the attributes for this node in XPathNodeSet c14n.
640      *
641      * @param element
642      * @param ns
643      * @param cache
644      * @throws CanonicalizationException, DOMException, IOException
645      */
outputAttributes(Element element, NameSpaceSymbTable ns, Map<String, byte[]> cache)646     abstract void outputAttributes(Element element, NameSpaceSymbTable ns, Map<String, byte[]> cache)
647         throws CanonicalizationException, DOMException, IOException;
648 
649     /**
650      * Output the attributes for this node in a Subtree c14n.
651      *
652      * @param element
653      * @param ns
654      * @param cache
655      * @throws CanonicalizationException, DOMException, IOException
656      */
outputAttributesSubtree(Element element, NameSpaceSymbTable ns, Map<String, byte[]> cache)657     abstract void outputAttributesSubtree(Element element, NameSpaceSymbTable ns, Map<String, byte[]> cache)
658         throws CanonicalizationException, DOMException, IOException;
659 
circumventBugIfNeeded(XMLSignatureInput input)660     abstract void circumventBugIfNeeded(XMLSignatureInput input)
661         throws CanonicalizationException, ParserConfigurationException, IOException, SAXException;
662 
663     /**
664      * Outputs an Attribute to the internal Writer.
665      *
666      * The string value of the node is modified by replacing
667      * <UL>
668      * <LI>all ampersands with {@code &amp;amp;}</LI>
669      * <LI>all open angle brackets with {@code &amp;lt;}</LI>
670      * <LI>all quotation mark characters with {@code &amp;quot;}</LI>
671      * <LI>and the whitespace characters {@code #x9}, #xA, and #xD, with character
672      * references. The character references are written in uppercase
673      * hexadecimal with no leading zeroes (for example, {@code #xD} is represented
674      * by the character reference {@code &amp;#xD;})</LI>
675      * </UL>
676      *
677      * @param name
678      * @param value
679      * @param writer
680      * @throws IOException
681      */
outputAttrToWriter( final String name, final String value, final OutputStream writer, final Map<String, byte[]> cache )682     protected static final void outputAttrToWriter(
683         final String name, final String value,
684         final OutputStream writer, final Map<String, byte[]> cache
685     ) throws IOException {
686         writer.write(' ');
687         UtfHelpper.writeByte(name, writer, cache);
688         writer.write(EQUALS_STR.clone());
689         byte[] toWrite;
690         final int length = value.length();
691         int i = 0;
692         while (i < length) {
693             int c = value.codePointAt(i);
694             i += Character.charCount(c);
695 
696             switch (c) {
697 
698             case '&' :
699                 toWrite = AMP.clone();
700                 break;
701 
702             case '<' :
703                 toWrite = LT.clone();
704                 break;
705 
706             case '"' :
707                 toWrite = QUOT.clone();
708                 break;
709 
710             case 0x09 :    // '\t'
711                 toWrite = X9.clone();
712                 break;
713 
714             case 0x0A :    // '\n'
715                 toWrite = XA.clone();
716                 break;
717 
718             case 0x0D :    // '\r'
719                 toWrite = XD.clone();
720                 break;
721 
722             default :
723                 if (c < 0x80) {
724                     writer.write(c);
725                 } else {
726                     UtfHelpper.writeCodePointToUtf8(c, writer);
727                 }
728                 continue;
729             }
730             writer.write(toWrite);
731         }
732 
733         writer.write('\"');
734     }
735 
736     /**
737      * Outputs a PI to the internal Writer.
738      *
739      * @param currentPI
740      * @param writer where to write the things
741      * @throws IOException
742      */
outputPItoWriter( ProcessingInstruction currentPI, OutputStream writer, int position )743     protected void outputPItoWriter(
744         ProcessingInstruction currentPI, OutputStream writer, int position
745     ) throws IOException {
746         if (position == NODE_AFTER_DOCUMENT_ELEMENT) {
747             writer.write('\n');
748         }
749         writer.write(BEGIN_PI.clone());
750 
751         final String target = currentPI.getTarget();
752         int length = target.length();
753 
754         for (int i = 0; i < length; ) {
755             int c = target.codePointAt(i);
756             i += Character.charCount(c);
757             if (c == 0x0D) {
758                 writer.write(XD.clone());
759             } else {
760                 if (c < 0x80) {
761                     writer.write(c);
762                 } else {
763                     UtfHelpper.writeCodePointToUtf8(c, writer);
764                 }
765             }
766         }
767 
768         final String data = currentPI.getData();
769 
770         length = data.length();
771 
772         if (length > 0) {
773             writer.write(' ');
774 
775             for (int i = 0; i < length; ) {
776                 int c = data.codePointAt(i);
777                 i += Character.charCount(c);
778                 if (c == 0x0D) {
779                     writer.write(XD.clone());
780                 } else {
781                     UtfHelpper.writeCodePointToUtf8(c, writer);
782                 }
783             }
784         }
785 
786         writer.write(END_PI.clone());
787         if (position == NODE_BEFORE_DOCUMENT_ELEMENT) {
788             writer.write('\n');
789         }
790     }
791 
792     /**
793      * Method outputCommentToWriter
794      *
795      * @param currentComment
796      * @param writer writer where to write the things
797      * @throws IOException
798      */
outputCommentToWriter( Comment currentComment, OutputStream writer, int position )799     protected void outputCommentToWriter(
800         Comment currentComment, OutputStream writer, int position
801     ) throws IOException {
802         if (position == NODE_AFTER_DOCUMENT_ELEMENT) {
803             writer.write('\n');
804         }
805         writer.write(BEGIN_COMM.clone());
806 
807         final String data = currentComment.getData();
808         final int length = data.length();
809 
810         for (int i = 0; i < length; ) {
811             int c = data.codePointAt(i);
812             i += Character.charCount(c);
813             if (c == 0x0D) {
814                 writer.write(XD.clone());
815             } else {
816                 if (c < 0x80) {
817                     writer.write(c);
818                 } else {
819                     UtfHelpper.writeCodePointToUtf8(c, writer);
820                 }
821             }
822         }
823 
824         writer.write(END_COMM.clone());
825         if (position == NODE_BEFORE_DOCUMENT_ELEMENT) {
826             writer.write('\n');
827         }
828     }
829 
830     /**
831      * Outputs a Text of CDATA section to the internal Writer.
832      *
833      * @param text
834      * @param writer writer where to write the things
835      * @throws IOException
836      */
outputTextToWriter( final String text, final OutputStream writer )837     protected static final void outputTextToWriter(
838         final String text, final OutputStream writer
839     ) throws IOException {
840         final int length = text.length();
841         byte[] toWrite;
842         for (int i = 0; i < length; ) {
843             int c = text.codePointAt(i);
844             i += Character.charCount(c);
845 
846             switch (c) {
847 
848             case '&' :
849                 toWrite = AMP.clone();
850                 break;
851 
852             case '<' :
853                 toWrite = LT.clone();
854                 break;
855 
856             case '>' :
857                 toWrite = GT.clone();
858                 break;
859 
860             case 0xD :
861                 toWrite = XD.clone();
862                 break;
863 
864             default :
865                 if (c < 0x80) {
866                     writer.write(c);
867                 } else {
868                     UtfHelpper.writeCodePointToUtf8(c, writer);
869                 }
870                 continue;
871             }
872             writer.write(toWrite);
873         }
874     }
875 
876     // The null xmlns definition.
getNullNode(Document ownerDocument)877     protected Attr getNullNode(Document ownerDocument) {
878         if (nullNode == null) {
879             try {
880                 nullNode = ownerDocument.createAttributeNS(
881                                     Constants.NamespaceSpecNS, XMLNS);
882                 nullNode.setValue("");
883             } catch (Exception e) {
884                 throw new RuntimeException("Unable to create nullNode: " + e);
885             }
886         }
887         return nullNode;
888     }
889 
890 }
891