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.signature;
24  
25  import java.io.ByteArrayInputStream;
26  import java.io.ByteArrayOutputStream;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.io.OutputStream;
30  import java.util.ArrayList;
31  import java.util.LinkedHashSet;
32  import java.util.List;
33  import java.util.Set;
34  
35  import javax.xml.XMLConstants;
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  
40  import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
41  import com.sun.org.apache.xml.internal.security.c14n.implementations.CanonicalizerBase;
42  import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer20010315OmitComments;
43  import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer11_OmitComments;
44  import com.sun.org.apache.xml.internal.security.exceptions.XMLSecurityRuntimeException;
45  import com.sun.org.apache.xml.internal.security.utils.JavaUtils;
46  import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
47  import org.w3c.dom.Document;
48  import org.w3c.dom.Node;
49  import org.xml.sax.SAXException;
50  
51  /**
52   * Class XMLSignatureInput
53   *
54   * @author Christian Geuer-Pollmann
55   * $todo$ check whether an XMLSignatureInput can be _both_, octet stream _and_ node set?
56   */
57  public class XMLSignatureInput {
58      /*
59       * The XMLSignature Input can be either:
60       *   A byteArray like with/or without InputStream.
61       *   Or a nodeSet like defined either:
62       *       * as a collection of nodes
63       *       * or as subnode excluding or not comments and excluding or
64       *         not other nodes.
65       */
66  
67      /**
68       * Some InputStreams do not support the {@link java.io.InputStream#reset}
69       * method, so we read it in completely and work on our Proxy.
70       */
71      private InputStream inputOctetStreamProxy = null;
72      /**
73       * The original NodeSet for this XMLSignatureInput
74       */
75      private Set<Node> inputNodeSet = null;
76      /**
77       * The original Element
78       */
79      private Node subNode = null;
80      /**
81       * Exclude Node *for enveloped transformations*
82       */
83      private Node excludeNode = null;
84      /**
85       *
86       */
87      private boolean excludeComments = false;
88  
89      private boolean isNodeSet = false;
90      /**
91       * A cached bytes
92       */
93      private byte[] bytes = null;
94  
95      /**
96       * Some Transforms may require explicit MIME type, charset (IANA registered
97       * "character set"), or other such information concerning the data they are
98       * receiving from an earlier Transform or the source data, although no
99       * Transform algorithm specified in this document needs such explicit
100      * information. Such data characteristics are provided as parameters to the
101      * Transform algorithm and should be described in the specification for the
102      * algorithm.
103      */
104     private String mimeType = null;
105 
106     /**
107      * Field sourceURI
108      */
109     private String sourceURI = null;
110 
111     /**
112      * Node Filter list.
113      */
114     private List<NodeFilter> nodeFilters = new ArrayList<NodeFilter>();
115 
116     private boolean needsToBeExpanded = false;
117     private OutputStream outputStream = null;
118 
119     private DocumentBuilderFactory dfactory;
120 
121     /**
122      * Construct a XMLSignatureInput from an octet array.
123      * <p>
124      * This is a comfort method, which internally converts the byte[] array into
125      * an InputStream
126      * <p>NOTE: no defensive copy</p>
127      * @param inputOctets an octet array which including XML document or node
128      */
129     public XMLSignatureInput(byte[] inputOctets) {
130         // NO defensive copy
131         this.bytes = inputOctets;
132     }
133 
134     /**
135      * Constructs a <code>XMLSignatureInput</code> from an octet stream. The
136      * stream is directly read.
137      *
138      * @param inputOctetStream
139      */
140     public XMLSignatureInput(InputStream inputOctetStream)  {
141         this.inputOctetStreamProxy = inputOctetStream;
142     }
143 
144     /**
145      * Construct a XMLSignatureInput from a subtree rooted by rootNode. This
146      * method included the node and <I>all</I> his descendants in the output.
147      *
148      * @param rootNode
149      */
150     public XMLSignatureInput(Node rootNode) {
151         this.subNode = rootNode;
152     }
153 
154     /**
155      * Constructor XMLSignatureInput
156      *
157      * @param inputNodeSet
158      */
159     public XMLSignatureInput(Set<Node> inputNodeSet) {
160         this.inputNodeSet = inputNodeSet;
161     }
162 
163     /**
164      * Check if the structure needs to be expanded.
165      * @return true if so.
166      */
167     public boolean isNeedsToBeExpanded() {
168         return needsToBeExpanded;
169     }
170 
171     /**
172      * Set if the structure needs to be expanded.
173      * @param needsToBeExpanded true if so.
174      */
175     public void setNeedsToBeExpanded(boolean needsToBeExpanded) {
176         this.needsToBeExpanded = needsToBeExpanded;
177     }
178 
179     /**
180      * Returns the node set from input which was specified as the parameter of
181      * {@link XMLSignatureInput} constructor
182      *
183      * @return the node set
184      * @throws SAXException
185      * @throws IOException
186      * @throws ParserConfigurationException
187      * @throws CanonicalizationException
188      */
189     public Set<Node> getNodeSet() throws CanonicalizationException, ParserConfigurationException,
190         IOException, SAXException {
191         return getNodeSet(false);
192     }
193 
194     /**
195      * Get the Input NodeSet.
196      * @return the Input NodeSet.
197      */
198     public Set<Node> getInputNodeSet() {
199         return inputNodeSet;
200     }
201 
202     /**
203      * Returns the node set from input which was specified as the parameter of
204      * {@link XMLSignatureInput} constructor
205      * @param circumvent
206      *
207      * @return the node set
208      * @throws SAXException
209      * @throws IOException
210      * @throws ParserConfigurationException
211      * @throws CanonicalizationException
212      */
213     public Set<Node> getNodeSet(boolean circumvent) throws ParserConfigurationException,
214         IOException, SAXException, CanonicalizationException {
215         if (inputNodeSet != null) {
216             return inputNodeSet;
217         }
218         if (inputOctetStreamProxy == null && subNode != null) {
219             if (circumvent) {
220                 XMLUtils.circumventBug2650(XMLUtils.getOwnerDocument(subNode));
221             }
222             inputNodeSet = new LinkedHashSet<Node>();
223             XMLUtils.getSet(subNode, inputNodeSet, excludeNode, excludeComments);
224             return inputNodeSet;
225         } else if (isOctetStream()) {
226             convertToNodes();
227             Set<Node> result = new LinkedHashSet<Node>();
228             XMLUtils.getSet(subNode, result, null, false);
229             return result;
230         }
231 
232         throw new RuntimeException("getNodeSet() called but no input data present");
233     }
234 
235     /**
236      * Returns the Octet stream(byte Stream) from input which was specified as
237      * the parameter of {@link XMLSignatureInput} constructor
238      *
239      * @return the Octet stream(byte Stream) from input which was specified as
240      * the parameter of {@link XMLSignatureInput} constructor
241      * @throws IOException
242      */
243     public InputStream getOctetStream() throws IOException  {
244         if (inputOctetStreamProxy != null) {
245             return inputOctetStreamProxy;
246         }
247 
248         if (bytes != null) {
249             inputOctetStreamProxy = new ByteArrayInputStream(bytes);
250             return inputOctetStreamProxy;
251         }
252 
253         return null;
254     }
255 
256     /**
257      * @return real octet stream
258      */
259     public InputStream getOctetStreamReal() {
260         return inputOctetStreamProxy;
261     }
262 
263     /**
264      * Returns the byte array from input which was specified as the parameter of
265      * {@link XMLSignatureInput} constructor
266      *
267      * @return the byte[] from input which was specified as the parameter of
268      * {@link XMLSignatureInput} constructor
269      *
270      * @throws CanonicalizationException
271      * @throws IOException
272      */
273     public byte[] getBytes() throws IOException, CanonicalizationException {
274         byte[] inputBytes = getBytesFromInputStream();
275         if (inputBytes != null) {
276             return inputBytes;
277         }
278         Canonicalizer20010315OmitComments c14nizer = new Canonicalizer20010315OmitComments();
279         bytes = c14nizer.engineCanonicalize(this);
280         return bytes;
281     }
282 
283     /**
284      * Determines if the object has been set up with a Node set
285      *
286      * @return true if the object has been set up with a Node set
287      */
288     public boolean isNodeSet() {
289         return ((inputOctetStreamProxy == null
290             && inputNodeSet != null) || isNodeSet);
291     }
292 
293     /**
294      * Determines if the object has been set up with an Element
295      *
296      * @return true if the object has been set up with an Element
297      */
298     public boolean isElement() {
299         return (inputOctetStreamProxy == null && subNode != null
300             && inputNodeSet == null && !isNodeSet);
301     }
302 
303     /**
304      * Determines if the object has been set up with an octet stream
305      *
306      * @return true if the object has been set up with an octet stream
307      */
308     public boolean isOctetStream() {
309         return ((inputOctetStreamProxy != null || bytes != null)
310           && (inputNodeSet == null && subNode == null));
311     }
312 
313     /**
314      * Determines if {@link #setOutputStream} has been called with a
315      * non-null OutputStream.
316      *
317      * @return true if {@link #setOutputStream} has been called with a
318      * non-null OutputStream
319      */
320     public boolean isOutputStreamSet() {
321         return outputStream != null;
322     }
323 
324     /**
325      * Determines if the object has been set up with a ByteArray
326      *
327      * @return true is the object has been set up with an octet stream
328      */
329     public boolean isByteArray() {
330         return (bytes != null && (this.inputNodeSet == null && subNode == null));
331     }
332 
333     /**
334      * Is the object correctly set up?
335      *
336      * @return true if the object has been set up correctly
337      */
338     public boolean isInitialized() {
339         return isOctetStream() || isNodeSet();
340     }
341 
342     /**
343      * Returns mimeType
344      *
345      * @return mimeType
346      */
347     public String getMIMEType() {
348         return mimeType;
349     }
350 
351     /**
352      * Sets mimeType
353      *
354      * @param mimeType
355      */
356     public void setMIMEType(String mimeType) {
357         this.mimeType = mimeType;
358     }
359 
360     /**
361      * Return SourceURI
362      *
363      * @return SourceURI
364      */
365     public String getSourceURI() {
366         return sourceURI;
367     }
368 
369     /**
370      * Sets SourceURI
371      *
372      * @param sourceURI
373      */
374     public void setSourceURI(String sourceURI) {
375         this.sourceURI = sourceURI;
376     }
377 
378     /**
379      * Method toString
380      * @inheritDoc
381      */
382     public String toString() {
383         if (isNodeSet()) {
384             return "XMLSignatureInput/NodeSet/" + inputNodeSet.size()
385                    + " nodes/" + getSourceURI();
386         }
387         if (isElement()) {
388             return "XMLSignatureInput/Element/" + subNode
389                 + " exclude "+ excludeNode + " comments:"
390                 + excludeComments +"/" + getSourceURI();
391         }
392         try {
393             return "XMLSignatureInput/OctetStream/" + getBytes().length
394                    + " octets/" + getSourceURI();
395         } catch (IOException iex) {
396             return "XMLSignatureInput/OctetStream//" + getSourceURI();
397         } catch (CanonicalizationException cex) {
398             return "XMLSignatureInput/OctetStream//" + getSourceURI();
399         }
400     }
401 
402     /**
403      * Method getHTMLRepresentation
404      *
405      * @throws XMLSignatureException
406      * @return The HTML representation for this XMLSignature
407      */
408     public String getHTMLRepresentation() throws XMLSignatureException {
409         XMLSignatureInputDebugger db = new XMLSignatureInputDebugger(this);
410         return db.getHTMLRepresentation();
411     }
412 
413     /**
414      * Method getHTMLRepresentation
415      *
416      * @param inclusiveNamespaces
417      * @throws XMLSignatureException
418      * @return The HTML representation for this XMLSignature
419      */
420     public String getHTMLRepresentation(Set<String> inclusiveNamespaces)
421        throws XMLSignatureException {
422         XMLSignatureInputDebugger db =
423             new XMLSignatureInputDebugger(this, inclusiveNamespaces);
424         return db.getHTMLRepresentation();
425     }
426 
427     /**
428      * Gets the exclude node of this XMLSignatureInput
429      * @return Returns the excludeNode.
430      */
431     public Node getExcludeNode() {
432         return excludeNode;
433     }
434 
435     /**
436      * Sets the exclude node of this XMLSignatureInput
437      * @param excludeNode The excludeNode to set.
438      */
439     public void setExcludeNode(Node excludeNode) {
440         this.excludeNode = excludeNode;
441     }
442 
443     /**
444      * Gets the node of this XMLSignatureInput
445      * @return The excludeNode set.
446      */
447     public Node getSubNode() {
448         return subNode;
449     }
450 
451     /**
452      * @return Returns the excludeComments.
453      */
454     public boolean isExcludeComments() {
455         return excludeComments;
456     }
457 
458     /**
459      * @param excludeComments The excludeComments to set.
460      */
461     public void setExcludeComments(boolean excludeComments) {
462         this.excludeComments = excludeComments;
463     }
464 
465     /**
466      * @param diOs
467      * @throws IOException
468      * @throws CanonicalizationException
469      */
470     public void updateOutputStream(OutputStream diOs)
471         throws CanonicalizationException, IOException {
472         updateOutputStream(diOs, false);
473     }
474 
475     public void updateOutputStream(OutputStream diOs, boolean c14n11)
476         throws CanonicalizationException, IOException {
477         if (diOs == outputStream) {
478             return;
479         }
480         if (bytes != null) {
481             diOs.write(bytes);
482         } else if (inputOctetStreamProxy == null) {
483             CanonicalizerBase c14nizer = null;
484             if (c14n11) {
485                 c14nizer = new Canonicalizer11_OmitComments();
486             } else {
487                 c14nizer = new Canonicalizer20010315OmitComments();
488             }
489             c14nizer.setWriter(diOs);
490             c14nizer.engineCanonicalize(this);
491         } else {
492             byte[] buffer = new byte[4 * 1024];
493             int bytesread = 0;
494             try {
495                 while ((bytesread = inputOctetStreamProxy.read(buffer)) != -1) {
496                     diOs.write(buffer, 0, bytesread);
497                 }
498             } catch (IOException ex) {
499                 inputOctetStreamProxy.close();
500                 throw ex;
501             }
502         }
503     }
504 
505     /**
506      * @param os
507      */
508     public void setOutputStream(OutputStream os) {
509         outputStream = os;
510     }
511 
512     private byte[] getBytesFromInputStream() throws IOException {
513         if (bytes != null) {
514             return bytes;
515         }
516         if (inputOctetStreamProxy == null) {
517             return null;
518         }
519         try {
520             bytes = JavaUtils.getBytesFromStream(inputOctetStreamProxy);
521         } finally {
522             inputOctetStreamProxy.close();
523         }
524         return bytes;
525     }
526 
527     /**
528      * @param filter
529      */
530     public void addNodeFilter(NodeFilter filter) {
531         if (isOctetStream()) {
532             try {
533                 convertToNodes();
534             } catch (Exception e) {
535                 throw new XMLSecurityRuntimeException(
536                     "signature.XMLSignatureInput.nodesetReference", e
537                 );
538             }
539         }
540         nodeFilters.add(filter);
541     }
542 
543     /**
544      * @return the node filters
545      */
546     public List<NodeFilter> getNodeFilters() {
547         return nodeFilters;
548     }
549 
550     /**
551      * @param b
552      */
553     public void setNodeSet(boolean b) {
554         isNodeSet = b;
555     }
556 
557     void convertToNodes() throws CanonicalizationException,
558         ParserConfigurationException, IOException, SAXException {
559         if (dfactory == null) {
560             dfactory = DocumentBuilderFactory.newInstance();
561             dfactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
562             dfactory.setValidating(false);
563             dfactory.setNamespaceAware(true);
564         }
565         DocumentBuilder db = dfactory.newDocumentBuilder();
566         // select all nodes, also the comments.
567         try {
568             db.setErrorHandler(new com.sun.org.apache.xml.internal.security.utils.IgnoreAllErrorHandler());
569 
570             Document doc = db.parse(this.getOctetStream());
571             this.subNode = doc;
572         } catch (SAXException ex) {
573             // if a not-wellformed nodeset exists, put a container around it...
574             ByteArrayOutputStream baos = new ByteArrayOutputStream();
575 
576             baos.write("<container>".getBytes("UTF-8"));
577             baos.write(this.getBytes());
578             baos.write("</container>".getBytes("UTF-8"));
579 
580             byte result[] = baos.toByteArray();
581             Document document = db.parse(new ByteArrayInputStream(result));
582             this.subNode = document.getDocumentElement().getFirstChild().getFirstChild();
583         } finally {
584             if (this.inputOctetStreamProxy != null) {
585                 this.inputOctetStreamProxy.close();
586             }
587             this.inputOctetStreamProxy = null;
588             this.bytes = null;
589         }
590     }
591 
592 }