1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 package org.apache.xml.security.c14n.implementations;
20 
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.UnsupportedEncodingException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.ListIterator;
30 import java.util.Map;
31 import java.util.Set;
32 
33 import javax.xml.parsers.DocumentBuilder;
34 import javax.xml.parsers.DocumentBuilderFactory;
35 import javax.xml.parsers.ParserConfigurationException;
36 
37 import org.apache.xml.security.c14n.CanonicalizationException;
38 import org.apache.xml.security.c14n.CanonicalizerSpi;
39 import org.apache.xml.security.c14n.helper.AttrCompare;
40 import org.apache.xml.security.signature.NodeFilter;
41 import org.apache.xml.security.signature.XMLSignatureInput;
42 import org.apache.xml.security.utils.Constants;
43 import org.apache.xml.security.utils.UnsyncByteArrayOutputStream;
44 import org.apache.xml.security.utils.XMLUtils;
45 import org.w3c.dom.Attr;
46 import org.w3c.dom.Comment;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.NamedNodeMap;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.ProcessingInstruction;
51 import org.xml.sax.SAXException;
52 
53 /**
54  * Abstract base class for canonicalization algorithms.
55  *
56  * @author Christian Geuer-Pollmann <geuerp@apache.org>
57  */
58 public abstract class CanonicalizerBase extends CanonicalizerSpi {
59     public static final String XML = "xml";
60     public static final String XMLNS = "xmlns";
61 
62     protected static final AttrCompare COMPARE = new AttrCompare();
63     protected static final Attr nullNode;
64 
65     private static final byte[] END_PI = {'?','>'};
66     private static final byte[] BEGIN_PI = {'<','?'};
67     private static final byte[] END_COMM = {'-','-','>'};
68     private static final byte[] BEGIN_COMM = {'<','!','-','-'};
69     private static final byte[] XA = {'&','#','x','A',';'};
70     private static final byte[] X9 = {'&','#','x','9',';'};
71     private static final byte[] QUOT = {'&','q','u','o','t',';'};
72     private static final byte[] XD = {'&','#','x','D',';'};
73     private static final byte[] GT = {'&','g','t',';'};
74     private static final byte[] LT = {'&','l','t',';'};
75     private static final byte[] END_TAG = {'<','/'};
76     private static final byte[] AMP = {'&','a','m','p',';'};
77     private static final byte[] equalsStr = {'=','\"'};
78 
79     protected static final int NODE_BEFORE_DOCUMENT_ELEMENT = -1;
80     protected static final int NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT = 0;
81     protected static final int NODE_AFTER_DOCUMENT_ELEMENT = 1;
82 
83     static {
84         // The null xmlns definition.
85         try {
86             DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
87             nullNode = documentBuilder.newDocument().createAttributeNS(Constants.NamespaceSpecNS, XMLNS);
88             nullNode.setValue("");
89         } catch (Exception e) {
90             throw new RuntimeException("Unable to create nullNode: " + e);
91         }
92     }
93 
94     private List<NodeFilter> nodeFilter;
95 
96     private boolean includeComments;
97     private Set<Node> xpathNodeSet;
98     /**
99      * The node to be skipped/excluded from the DOM tree
100      * in subtree canonicalizations.
101      */
102     private Node excludeNode;
103     private OutputStream writer = new ByteArrayOutputStream();
104 
105     /**
106      * Constructor CanonicalizerBase
107      *
108      * @param includeComments
109      */
CanonicalizerBase(boolean includeComments)110     public CanonicalizerBase(boolean includeComments) {
111         this.includeComments = includeComments;
112     }
113 
114     /**
115      * Method engineCanonicalizeSubTree
116      * @inheritDoc
117      * @param rootNode
118      * @throws CanonicalizationException
119      */
engineCanonicalizeSubTree(Node rootNode)120     public byte[] engineCanonicalizeSubTree(Node rootNode)
121         throws CanonicalizationException {
122         return engineCanonicalizeSubTree(rootNode, (Node)null);
123     }
124 
125     /**
126      * Method engineCanonicalizeXPathNodeSet
127      * @inheritDoc
128      * @param xpathNodeSet
129      * @throws CanonicalizationException
130      */
engineCanonicalizeXPathNodeSet(Set<Node> xpathNodeSet)131     public byte[] engineCanonicalizeXPathNodeSet(Set<Node> xpathNodeSet)
132         throws CanonicalizationException {
133         this.xpathNodeSet = xpathNodeSet;
134         return engineCanonicalizeXPathNodeSetInternal(XMLUtils.getOwnerDocument(this.xpathNodeSet));
135     }
136 
137     /**
138      * Canonicalizes a Subtree node.
139      * @param input the root of the subtree to canicalize
140      * @return The canonicalize stream.
141      * @throws CanonicalizationException
142      */
engineCanonicalize(XMLSignatureInput input)143     public byte[] engineCanonicalize(XMLSignatureInput input) throws CanonicalizationException {
144         try {
145             if (input.isExcludeComments()) {
146                 includeComments = false;
147             }
148             if (input.isOctetStream()) {
149                 return engineCanonicalize(input.getBytes());
150             }
151             if (input.isElement()) {
152                 return engineCanonicalizeSubTree(input.getSubNode(), input.getExcludeNode());
153             } else if (input.isNodeSet()) {
154                 nodeFilter = input.getNodeFilters();
155 
156                 circumventBugIfNeeded(input);
157 
158                 if (input.getSubNode() != null) {
159                     return engineCanonicalizeXPathNodeSetInternal(input.getSubNode());
160                 } else {
161                     return engineCanonicalizeXPathNodeSet(input.getNodeSet());
162                 }
163             }
164             return null;
165         } catch (CanonicalizationException ex) {
166             throw new CanonicalizationException("empty", ex);
167         } catch (ParserConfigurationException ex) {
168             throw new CanonicalizationException("empty", ex);
169         } catch (IOException ex) {
170             throw new CanonicalizationException("empty", ex);
171         } catch (SAXException ex) {
172             throw new CanonicalizationException("empty", 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 
183     /**
184      * Canonicalizes a Subtree node.
185      *
186      * @param rootNode
187      *            the root of the subtree to canonicalize
188      * @param excludeNode
189      *            a node to be excluded from the canonicalize operation
190      * @return The canonicalize stream.
191      * @throws CanonicalizationException
192      */
engineCanonicalizeSubTree(Node rootNode, Node excludeNode)193     protected byte[] engineCanonicalizeSubTree(Node rootNode, Node excludeNode)
194         throws CanonicalizationException {
195         this.excludeNode = excludeNode;
196         try {
197             NameSpaceSymbTable ns = new NameSpaceSymbTable();
198             int nodeLevel = NODE_BEFORE_DOCUMENT_ELEMENT;
199             if (rootNode != null && Node.ELEMENT_NODE == rootNode.getNodeType()) {
200                 //Fills the nssymbtable with the definitions of the parent of the root subnode
201                 getParentNameSpaces((Element)rootNode, ns);
202                 nodeLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
203             }
204             this.canonicalizeSubTree(rootNode, ns, rootNode, nodeLevel);
205             this.writer.flush();
206             if (this.writer instanceof ByteArrayOutputStream) {
207                 byte[] result = ((ByteArrayOutputStream)this.writer).toByteArray();
208                 if (reset) {
209                     ((ByteArrayOutputStream)this.writer).reset();
210                 } else {
211                     this.writer.close();
212                 }
213                 return result;
214             } else if (this.writer instanceof UnsyncByteArrayOutputStream) {
215                 byte[] result = ((UnsyncByteArrayOutputStream)this.writer).toByteArray();
216                 if (reset) {
217                     ((UnsyncByteArrayOutputStream)this.writer).reset();
218                 } else {
219                     this.writer.close();
220                 }
221                 return result;
222             } else {
223                 this.writer.close();
224             }
225             return null;
226 
227         } catch (UnsupportedEncodingException ex) {
228             throw new CanonicalizationException("empty", ex);
229         } catch (IOException ex) {
230             throw new CanonicalizationException("empty", ex);
231         }
232     }
233 
234 
235     /**
236      * Method canonicalizeSubTree, this function is a recursive one.
237      *
238      * @param currentNode
239      * @param ns
240      * @param endnode
241      * @throws CanonicalizationException
242      * @throws IOException
243      */
canonicalizeSubTree( Node currentNode, NameSpaceSymbTable ns, Node endnode, int documentLevel )244     protected final void canonicalizeSubTree(
245         Node currentNode, NameSpaceSymbTable ns, Node endnode, int documentLevel
246     ) throws CanonicalizationException, IOException {
247         if (isVisibleInt(currentNode) == -1) {
248             return;
249         }
250         Node sibling = null;
251         Node parentNode = null;
252         final OutputStream writer = this.writer;
253         final Node excludeNode = this.excludeNode;
254         final boolean includeComments = this.includeComments;
255         Map<String, byte[]> cache = new HashMap<String, byte[]>();
256         do {
257             switch (currentNode.getNodeType()) {
258 
259             case Node.ENTITY_NODE :
260             case Node.NOTATION_NODE :
261             case Node.ATTRIBUTE_NODE :
262                 // illegal node type during traversal
263                 throw new CanonicalizationException("empty");
264 
265             case Node.DOCUMENT_FRAGMENT_NODE :
266             case Node.DOCUMENT_NODE :
267                 ns.outputNodePush();
268                 sibling = currentNode.getFirstChild();
269                 break;
270 
271             case Node.COMMENT_NODE :
272                 if (includeComments) {
273                     outputCommentToWriter((Comment) currentNode, writer, documentLevel);
274                 }
275                 break;
276 
277             case Node.PROCESSING_INSTRUCTION_NODE :
278                 outputPItoWriter((ProcessingInstruction) currentNode, writer, documentLevel);
279                 break;
280 
281             case Node.TEXT_NODE :
282             case Node.CDATA_SECTION_NODE :
283                 outputTextToWriter(currentNode.getNodeValue(), writer);
284                 break;
285 
286             case Node.ELEMENT_NODE :
287                 documentLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
288                 if (currentNode == excludeNode) {
289                     break;
290                 }
291                 Element currentElement = (Element)currentNode;
292                 //Add a level to the nssymbtable. So latter can be pop-back.
293                 ns.outputNodePush();
294                 writer.write('<');
295                 String name = currentElement.getTagName();
296                 UtfHelpper.writeByte(name, writer, cache);
297 
298                 Iterator<Attr> attrs = this.handleAttributesSubtree(currentElement, ns);
299                 if (attrs != null) {
300                     //we output all Attrs which are available
301                     while (attrs.hasNext()) {
302                         Attr attr = attrs.next();
303                         outputAttrToWriter(attr.getNodeName(), attr.getNodeValue(), writer, cache);
304                     }
305                 }
306                 writer.write('>');
307                 sibling = currentNode.getFirstChild();
308                 if (sibling == null) {
309                     writer.write(END_TAG);
310                     UtfHelpper.writeStringToUtf8(name, writer);
311                     writer.write('>');
312                     //We finished with this level, pop to the previous definitions.
313                     ns.outputNodePop();
314                     if (parentNode != null) {
315                         sibling = currentNode.getNextSibling();
316                     }
317                 } else {
318                     parentNode = currentElement;
319                 }
320                 break;
321 
322             case Node.DOCUMENT_TYPE_NODE :
323             default :
324                 break;
325             }
326             while (sibling == null && parentNode != null) {
327                 writer.write(END_TAG);
328                 UtfHelpper.writeByte(((Element)parentNode).getTagName(), writer, cache);
329                 writer.write('>');
330                 //We finished with this level, pop to the previous definitions.
331                 ns.outputNodePop();
332                 if (parentNode == endnode) {
333                     return;
334                 }
335                 sibling = parentNode.getNextSibling();
336                 parentNode = parentNode.getParentNode();
337                 if (parentNode == null || Node.ELEMENT_NODE != parentNode.getNodeType()) {
338                     documentLevel = NODE_AFTER_DOCUMENT_ELEMENT;
339                     parentNode = null;
340                 }
341             }
342             if (sibling == null) {
343                 return;
344             }
345             currentNode = sibling;
346             sibling = currentNode.getNextSibling();
347         } while(true);
348     }
349 
350 
engineCanonicalizeXPathNodeSetInternal(Node doc)351     private byte[] engineCanonicalizeXPathNodeSetInternal(Node doc)
352         throws CanonicalizationException {
353         try {
354             this.canonicalizeXPathNodeSet(doc, doc);
355             this.writer.flush();
356             if (this.writer instanceof ByteArrayOutputStream) {
357                 byte[] sol = ((ByteArrayOutputStream)this.writer).toByteArray();
358                 if (reset) {
359                     ((ByteArrayOutputStream)this.writer).reset();
360                 } else {
361                     this.writer.close();
362                 }
363                 return sol;
364             } else if (this.writer instanceof UnsyncByteArrayOutputStream) {
365                 byte[] result = ((UnsyncByteArrayOutputStream)this.writer).toByteArray();
366                 if (reset) {
367                     ((UnsyncByteArrayOutputStream)this.writer).reset();
368                 } else {
369                     this.writer.close();
370                 }
371                 return result;
372             } else {
373                 this.writer.close();
374             }
375             return null;
376         } catch (UnsupportedEncodingException ex) {
377             throw new CanonicalizationException("empty", ex);
378         } catch (IOException ex) {
379             throw new CanonicalizationException("empty", ex);
380         }
381     }
382 
383     /**
384      * Canonicalizes all the nodes included in the currentNode and contained in the
385      * xpathNodeSet field.
386      *
387      * @param currentNode
388      * @param endnode
389      * @throws CanonicalizationException
390      * @throws IOException
391      */
canonicalizeXPathNodeSet(Node currentNode, Node endnode)392     protected final void canonicalizeXPathNodeSet(Node currentNode, Node endnode)
393         throws CanonicalizationException, IOException {
394         if (isVisibleInt(currentNode) == -1) {
395             return;
396         }
397         boolean currentNodeIsVisible = false;
398         NameSpaceSymbTable ns = new NameSpaceSymbTable();
399         if (currentNode != null && Node.ELEMENT_NODE == currentNode.getNodeType()) {
400             getParentNameSpaces((Element)currentNode, ns);
401         }
402         if (currentNode == null) {
403             return;
404         }
405         Node sibling = null;
406         Node parentNode = null;
407         OutputStream writer = this.writer;
408         int documentLevel = NODE_BEFORE_DOCUMENT_ELEMENT;
409         Map<String, byte[]> cache = new HashMap<String, byte[]>();
410         do {
411             switch (currentNode.getNodeType()) {
412 
413             case Node.ENTITY_NODE :
414             case Node.NOTATION_NODE :
415             case Node.ATTRIBUTE_NODE :
416                 // illegal node type during traversal
417                 throw new CanonicalizationException("empty");
418 
419             case Node.DOCUMENT_FRAGMENT_NODE :
420             case Node.DOCUMENT_NODE :
421                 ns.outputNodePush();
422                 sibling = currentNode.getFirstChild();
423                 break;
424 
425             case Node.COMMENT_NODE :
426                 if (this.includeComments && (isVisibleDO(currentNode, ns.getLevel()) == 1)) {
427                     outputCommentToWriter((Comment) currentNode, writer, documentLevel);
428                 }
429                 break;
430 
431             case Node.PROCESSING_INSTRUCTION_NODE :
432                 if (isVisible(currentNode)) {
433                     outputPItoWriter((ProcessingInstruction) currentNode, writer, documentLevel);
434                 }
435                 break;
436 
437             case Node.TEXT_NODE :
438             case Node.CDATA_SECTION_NODE :
439                 if (isVisible(currentNode)) {
440                     outputTextToWriter(currentNode.getNodeValue(), writer);
441                     for (Node nextSibling = currentNode.getNextSibling();
442                         (nextSibling != null) && ((nextSibling.getNodeType() == Node.TEXT_NODE)
443                             || (nextSibling.getNodeType() == Node.CDATA_SECTION_NODE));
444                         nextSibling = nextSibling.getNextSibling()) {
445                         outputTextToWriter(nextSibling.getNodeValue(), writer);
446                         currentNode = nextSibling;
447                         sibling = currentNode.getNextSibling();
448                     }
449                 }
450                 break;
451 
452             case Node.ELEMENT_NODE :
453                 documentLevel = NODE_NOT_BEFORE_OR_AFTER_DOCUMENT_ELEMENT;
454                 Element currentElement = (Element) currentNode;
455                 //Add a level to the nssymbtable. So latter can be pop-back.
456                 String name = null;
457                 int i = isVisibleDO(currentNode, ns.getLevel());
458                 if (i == -1) {
459                     sibling = currentNode.getNextSibling();
460                     break;
461                 }
462                 currentNodeIsVisible = (i == 1);
463                 if (currentNodeIsVisible) {
464                     ns.outputNodePush();
465                     writer.write('<');
466                     name = currentElement.getTagName();
467                     UtfHelpper.writeByte(name, writer, cache);
468                 } else {
469                     ns.push();
470                 }
471 
472                 Iterator<Attr> attrs = handleAttributes(currentElement,ns);
473                 if (attrs != null) {
474                     //we output all Attrs which are available
475                     while (attrs.hasNext()) {
476                         Attr attr = attrs.next();
477                         outputAttrToWriter(attr.getNodeName(), attr.getNodeValue(), writer, cache);
478                     }
479                 }
480                 if (currentNodeIsVisible) {
481                     writer.write('>');
482                 }
483                 sibling = currentNode.getFirstChild();
484 
485                 if (sibling == null) {
486                     if (currentNodeIsVisible) {
487                         writer.write(END_TAG);
488                         UtfHelpper.writeByte(name, writer, cache);
489                         writer.write('>');
490                         //We finished with this level, pop to the previous definitions.
491                         ns.outputNodePop();
492                     } else {
493                         ns.pop();
494                     }
495                     if (parentNode != null) {
496                         sibling = currentNode.getNextSibling();
497                     }
498                 } else {
499                     parentNode = currentElement;
500                 }
501                 break;
502 
503             case Node.DOCUMENT_TYPE_NODE :
504             default :
505                 break;
506             }
507             while (sibling == null && parentNode != null) {
508                 if (isVisible(parentNode)) {
509                     writer.write(END_TAG);
510                     UtfHelpper.writeByte(((Element)parentNode).getTagName(), writer, cache);
511                     writer.write('>');
512                     //We finished with this level, pop to the previous definitions.
513                     ns.outputNodePop();
514                 } else {
515                     ns.pop();
516                 }
517                 if (parentNode == endnode) {
518                     return;
519                 }
520                 sibling = parentNode.getNextSibling();
521                 parentNode = parentNode.getParentNode();
522                 if (parentNode == null || Node.ELEMENT_NODE != parentNode.getNodeType()) {
523                     parentNode = null;
524                     documentLevel = NODE_AFTER_DOCUMENT_ELEMENT;
525                 }
526             }
527             if (sibling == null) {
528                 return;
529             }
530             currentNode = sibling;
531             sibling = currentNode.getNextSibling();
532         } while(true);
533     }
534 
isVisibleDO(Node currentNode, int level)535     protected int isVisibleDO(Node currentNode, int level) {
536         if (nodeFilter != null) {
537             Iterator<NodeFilter> it = nodeFilter.iterator();
538             while (it.hasNext()) {
539                 int i = (it.next()).isNodeIncludeDO(currentNode, level);
540                 if (i != 1) {
541                     return i;
542                 }
543             }
544         }
545         if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) {
546             return 0;
547         }
548         return 1;
549     }
550 
isVisibleInt(Node currentNode)551     protected int isVisibleInt(Node currentNode) {
552         if (nodeFilter != null) {
553             Iterator<NodeFilter> it = nodeFilter.iterator();
554             while (it.hasNext()) {
555                 int i = (it.next()).isNodeInclude(currentNode);
556                 if (i != 1) {
557                     return i;
558                 }
559             }
560         }
561         if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) {
562             return 0;
563         }
564         return 1;
565     }
566 
isVisible(Node currentNode)567     protected boolean isVisible(Node currentNode) {
568         if (nodeFilter != null) {
569             Iterator<NodeFilter> it = nodeFilter.iterator();
570             while (it.hasNext()) {
571                 if (it.next().isNodeInclude(currentNode) != 1) {
572                     return false;
573                 }
574             }
575         }
576         if ((this.xpathNodeSet != null) && !this.xpathNodeSet.contains(currentNode)) {
577             return false;
578         }
579         return true;
580     }
581 
handleParent(Element e, NameSpaceSymbTable ns)582     protected void handleParent(Element e, NameSpaceSymbTable ns) {
583         if (!e.hasAttributes() && e.getNamespaceURI() == null) {
584             return;
585         }
586         NamedNodeMap attrs = e.getAttributes();
587         int attrsLength = attrs.getLength();
588         for (int i = 0; i < attrsLength; i++) {
589             Attr attribute = (Attr) attrs.item(i);
590             String NName = attribute.getLocalName();
591             String NValue = attribute.getNodeValue();
592 
593             if (Constants.NamespaceSpecNS.equals(attribute.getNamespaceURI())
594                 && (!XML.equals(NName) || !Constants.XML_LANG_SPACE_SpecNS.equals(NValue))) {
595                 ns.addMapping(NName, NValue, attribute);
596             }
597         }
598         if (e.getNamespaceURI() != null) {
599             String NName = e.getPrefix();
600             String NValue = e.getNamespaceURI();
601             String Name;
602             if (NName == null || NName.equals("")) {
603                 NName = XMLNS;
604                 Name = XMLNS;
605             } else {
606                 Name = XMLNS + ":" + NName;
607             }
608             Attr n = e.getOwnerDocument().createAttributeNS("http://www.w3.org/2000/xmlns/", Name);
609             n.setValue(NValue);
610             ns.addMapping(NName, NValue, n);
611         }
612     }
613 
614     /**
615      * Adds to ns the definitions from the parent elements of el
616      * @param el
617      * @param ns
618      */
getParentNameSpaces(Element el, NameSpaceSymbTable ns)619     protected final void getParentNameSpaces(Element el, NameSpaceSymbTable ns)  {
620         Node n1 = el.getParentNode();
621         if (n1 == null || Node.ELEMENT_NODE != n1.getNodeType()) {
622             return;
623         }
624         //Obtain all the parents of the element
625         List<Element> parents = new ArrayList<Element>();
626         Node parent = n1;
627         while (parent != null && Node.ELEMENT_NODE == parent.getNodeType()) {
628             parents.add((Element)parent);
629             parent = parent.getParentNode();
630         }
631         //Visit them in reverse order.
632         ListIterator<Element> it = parents.listIterator(parents.size());
633         while (it.hasPrevious()) {
634             Element ele = it.previous();
635             handleParent(ele, ns);
636         }
637         parents.clear();
638         Attr nsprefix;
639         if (((nsprefix = ns.getMappingWithoutRendered(XMLNS)) != null)
640             && "".equals(nsprefix.getValue())) {
641             ns.addMappingAndRender(XMLNS, "", nullNode);
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      */
handleAttributes(Element element, NameSpaceSymbTable ns)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      */
handleAttributesSubtree(Element element, NameSpaceSymbTable ns)664     abstract Iterator<Attr> handleAttributesSubtree(Element element, NameSpaceSymbTable ns)
665         throws CanonicalizationException;
666 
circumventBugIfNeeded(XMLSignatureInput input)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      */
outputAttrToWriter( final String name, final String value, final OutputStream writer, final Map<String, byte[]> cache )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(equalsStr);
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;
706                 break;
707 
708             case '<' :
709                 toWrite = LT;
710                 break;
711 
712             case '"' :
713                 toWrite = QUOT;
714                 break;
715 
716             case 0x09 :    // '\t'
717                 toWrite = X9;
718                 break;
719 
720             case 0x0A :    // '\n'
721                 toWrite = XA;
722                 break;
723 
724             case 0x0D :    // '\r'
725                 toWrite = XD;
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      */
outputPItoWriter( ProcessingInstruction currentPI, OutputStream writer, int position )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);
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);
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);
784                 } else {
785                     UtfHelpper.writeCharToUtf8(c, writer);
786                 }
787             }
788         }
789 
790         writer.write(END_PI);
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      */
outputCommentToWriter( Comment currentComment, OutputStream writer, int position )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);
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);
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);
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      */
outputTextToWriter( final String text, final OutputStream writer )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;
852                 break;
853 
854             case '<' :
855                 toWrite = LT;
856                 break;
857 
858             case '>' :
859                 toWrite = GT;
860                 break;
861 
862             case 0xD :
863                 toWrite = XD;
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 }
879