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.io.StringReader;
21  
22  import org.w3c.dom.bootstrap.DOMImplementationRegistry;
23  import org.w3c.dom.ls.DOMImplementationLS;
24  import org.w3c.dom.ls.LSException;
25  import org.w3c.dom.ls.LSInput;
26  import org.w3c.dom.ls.LSResourceResolver;
27  
28  /**
29   * {@link LSResourceResolver} floor: consults an optional caller-supplied resolver and ignores (resolves to empty) whatever the caller does not resolve.
30   * <p>
31   * The schema-compile counterpart of {@link FallbackIgnoreEntityResolver2}. The secure {@link javax.xml.validation.SchemaFactory},
32   * {@link javax.xml.validation.Validator} and {@link javax.xml.validation.ValidatorHandler} wrappers install one of these and route a caller-set resolver
33   * through {@link #setDelegate} rather than letting it replace the floor. A caller opts a specific resource in by returning a non-{@code null} {@link LSInput};
34   * anything left unresolved resolves to an empty {@link LSInput}, so the external resource is neither fetched nor leaked.
35   * </p>
36   */
37  final class FallbackIgnoreLSResourceResolver implements LSResourceResolver {
38  
39      /**
40       * DOM Level 3 Load/Save implementation used to build the empty input for unresolved lookups.
41       */
42      private static final DOMImplementationLS DOM_LS;
43  
44      static {
45          try {
46              DOM_LS = (DOMImplementationLS) DOMImplementationRegistry.newInstance().getDOMImplementation("LS");
47          } catch (final ReflectiveOperationException e) {
48              throw new ExceptionInInitializerError(e);
49          }
50      }
51  
52      private LSResourceResolver delegate;
53  
54      /**
55       * Constructs a new resolver that consults the given delegate and ignores whatever it does not resolve.
56       *
57       * @param delegate optional caller-supplied resolver to consult first; may be {@code null}.
58       */
59      FallbackIgnoreLSResourceResolver(final LSResourceResolver delegate) {
60          this.delegate = delegate;
61      }
62  
63      /**
64       * Gets the delegate provided by the constructor or set by {@link #setDelegate}, or {@code null}.
65       *
66       * @return The delegate provided by the constructor or set by {@link #setDelegate}, or {@code null}.
67       */
68      LSResourceResolver getDelegate() {
69          return delegate;
70      }
71  
72      @Override
73      public LSInput resolveResource(final String type, final String namespaceURI, final String publicId, final String systemId, final String baseURI) {
74          final LSInput resolved = delegate != null ? delegate.resolveResource(type, namespaceURI, publicId, systemId, baseURI) : null;
75          if (resolved != null) {
76              return resolved;
77          }
78          if (SecureException.throwOnUnresolved()) {
79              // The interface declares no checked exception; LSException is the DOM Load/Save runtime failure type.
80              throw new LSException(LSException.PARSE_ERR, SecureException.forbidden(type, namespaceURI, publicId, systemId, baseURI));
81          }
82          // A character stream, not setStringData(""): the JDK's DOMEntityResolverWrapper discards empty string data, leaving a source with no content and a
83          // null system ID that Xerces then fails to absolutize. The echoed identifiers give Xerces a valid base URI; the content still comes from this
84          // empty stream, so nothing is fetched.
85          final LSInput empty = DOM_LS.createLSInput();
86          empty.setCharacterStream(new StringReader(""));
87          empty.setPublicId(publicId);
88          empty.setSystemId(systemId);
89          empty.setBaseURI(baseURI);
90          return empty;
91      }
92  
93      /**
94       * Sets the delegate to consult first, replacing any previous delegate. A {@code null} value removes the delegate and leaves a pure ignore-all floor.
95       *
96       * @param delegate The delegate to consult first, or {@code null} for a pure ignore-all floor.
97       */
98      void setDelegate(final LSResourceResolver delegate) {
99          this.delegate = delegate;
100     }
101 }