View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.xml.secure;
19  
20  import java.util.function.BooleanSupplier;
21  import java.util.function.Supplier;
22  
23  import javax.xml.parsers.DocumentBuilderFactory;
24  import javax.xml.parsers.FactoryConfigurationError;
25  import javax.xml.parsers.ParserConfigurationException;
26  import javax.xml.transform.Source;
27  import javax.xml.transform.TransformerException;
28  import javax.xml.transform.URIResolver;
29  import javax.xml.transform.dom.DOMSource;
30  
31  import org.w3c.dom.Document;
32  
33  /**
34   * {@link URIResolver} floor: consults an optional caller-supplied resolver and ignores (resolves to empty) whatever the caller does not resolve.
35   * <p>
36   * The XSLT counterpart of {@link FallbackIgnoreEntityResolver2}, guarding {@code xsl:import}/{@code xsl:include} at compile time and {@code document()} at
37   * transform time. The secure {@link javax.xml.transform.TransformerFactory} and {@link javax.xml.transform.Transformer} wrappers install one of these and
38   * route a caller-set resolver through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific URI in by returning a
39   * non-{@code null} {@link Source}; anything left unresolved resolves to an empty {@link Source}, so the external resource is neither fetched nor leaked.
40   * </p>
41   * <p>
42   * The shape of that empty {@link Source} is supplied by the caller: the default is a fresh, well-formed empty DOM document per resolution (which every stock
43   * TrAX consumer accepts), while the Saxon path supplies {@code EmptySource.getInstance()} so its consumers get the "empty" shape they expect.
44   * </p>
45   * <p>
46   * An opted-in {@link javax.xml.transform.stream.StreamSource} or reader-less {@link javax.xml.transform.sax.SAXSource} is rewritten to carry a secure reader
47   * before it is returned, so the implementation parses the opted-in content on the same floor instead of with an internal reader at its own defaults. A
48   * {@link javax.xml.transform.dom.DOMSource} or a {@link javax.xml.transform.sax.SAXSource} carrying the caller's own reader is returned as-is.
49   * </p>
50   */
51  final class FallbackIgnoreURIResolver implements URIResolver {
52  
53      /**
54       * Creates the empty document backing the default ignore outcome.
55       * <p>
56       * Consumers parse the resolved {@link Source}, and an empty character stream is not a well-formed XML document (XSLTC rejects it for {@code document()} and
57       * for an ignored {@code xsl:include}/{@code xsl:import}), so the default supplier answers with a well-formed empty document that evaluates to no content.
58       * </p>
59       * <p>
60       * The document is exposed to the consumer with the resolved {@link Source}, so each resolution gets its own: whatever a consumer does to a document it
61       * received cannot surface in another resolution.
62       * </p>
63       *
64       * @param factory The factory to create the document builder with.
65       * @return a new empty document.
66       * @throws IllegalStateException Thrown if the factory cannot supply a {@link javax.xml.parsers.DocumentBuilder} satisfying its configuration.
67       */
68      private static Document newEmptyDocument(final DocumentBuilderFactory factory) {
69          try {
70              return factory.newDocumentBuilder().newDocument();
71          } catch (final ParserConfigurationException e) {
72              throw new IllegalStateException(e);
73          }
74      }
75  
76      private URIResolver delegate;
77  
78      /**
79       * Produces the empty {@link Source} returned for an unresolved reference.
80       */
81      private final Supplier<Source> emptySource;
82  
83      /**
84       * Whether the opted-in rewrite should use the pluggable parser lookup instead of the platform's built-in parser; read per resolution so the factory-level floor tracks a later
85       * {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} toggle.
86       */
87      private final BooleanSupplier overrideDefaultParser;
88  
89      /**
90       * Constructs a new resolver.
91       *
92       * @param delegate         The resolver to delegate resolution to; may be {@code null}.
93       * @param emptySource      The empty-{@link Source} supplier for the ignore outcome, or {@code null} for the default empty DOM document.
94       * @param overrideDefaultParser whether the opted-in rewrite should use the pluggable parser lookup instead of the platform's built-in parser, read at each resolution.
95       */
96      FallbackIgnoreURIResolver(final URIResolver delegate, final Supplier<Source> emptySource, final BooleanSupplier overrideDefaultParser) {
97          this.delegate = delegate;
98          this.emptySource = emptySource != null ? emptySource
99                  : () -> new DOMSource(newEmptyDocument(SecureDocumentBuilderFactory.newNSInstance(overrideDefaultParser.getAsBoolean())));
100         this.overrideDefaultParser = overrideDefaultParser;
101     }
102 
103     /**
104      * Gets the delegate provided by the constructor or set by {@link #setDelegate}, may be {@code null}.
105      *
106      * @return The delegate provided by the constructor or set by {@link #setDelegate}, may be {@code null}.
107      */
108     URIResolver getDelegate() {
109         return delegate;
110     }
111 
112     /**
113      * {@inheritDoc}
114      *
115      * @throws FactoryConfigurationError Thrown from a factory in case of a {@link java.util.ServiceConfigurationError service
116      *                                   configuration error} or if the implementation is not available or cannot be instantiated.
117      */
118     @Override
119     public Source resolve(final String href, final String base) throws TransformerException {
120         final Source resolved = delegate != null ? delegate.resolve(href, base) : null;
121         if (resolved != null) {
122             // The implementation parses the opted-in handle with an internal reader at its own defaults; the rewrite hands it a secure reader instead.
123             return SecureSAXParserFactory.secure(resolved, overrideDefaultParser.getAsBoolean());
124         }
125         if (SecureException.throwOnUnresolved()) {
126             throw new TransformerException(SecureException.forbidden("uri", null, null, href, base));
127         }
128         return emptySource.get();
129     }
130 
131     /**
132      * Sets the delegate to consult first, replacing any previous delegate. A {@code null} value removes the delegate and leaves a pure ignore-all floor.
133      *
134      * @param delegate The delegate to consult first, or {@code null} for a pure ignore-all floor.
135      */
136     void setDelegate(final URIResolver delegate) {
137         this.delegate = delegate;
138     }
139 }