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 */
23package com.sun.org.apache.xml.internal.security.signature;
24
25import java.io.ByteArrayInputStream;
26import java.io.ByteArrayOutputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.io.OutputStream;
30import java.util.ArrayList;
31import java.util.LinkedHashSet;
32import java.util.List;
33import java.util.Set;
34
35import javax.xml.XMLConstants;
36import javax.xml.parsers.DocumentBuilder;
37import javax.xml.parsers.DocumentBuilderFactory;
38import javax.xml.parsers.ParserConfigurationException;
39
40import com.sun.org.apache.xml.internal.security.c14n.CanonicalizationException;
41import com.sun.org.apache.xml.internal.security.c14n.implementations.CanonicalizerBase;
42import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer20010315OmitComments;
43import com.sun.org.apache.xml.internal.security.c14n.implementations.Canonicalizer11_OmitComments;
44import com.sun.org.apache.xml.internal.security.exceptions.XMLSecurityRuntimeException;
45import com.sun.org.apache.xml.internal.security.utils.JavaUtils;
46import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
47import org.w3c.dom.Document;
48import org.w3c.dom.Node;
49import 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 */
57public 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}
593