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.ByteArrayInputStream;
21  import java.io.IOException;
22  import java.net.URI;
23  import java.net.URISyntaxException;
24  
25  import org.xml.sax.EntityResolver;
26  import org.xml.sax.InputSource;
27  import org.xml.sax.SAXException;
28  import org.xml.sax.ext.DefaultHandler2;
29  import org.xml.sax.ext.EntityResolver2;
30  
31  /**
32   * Entity resolver that consults an optional caller-supplied resolver and ignores (resolves to empty) whatever the caller does not resolve.
33   * <p>
34   * The canonical secure floor, and the entity-resolution counterpart of the JAXP 1.5 {@code ACCESS_EXTERNAL_*} properties. Every floor
35   * ({@link FallbackIgnoreLSResourceResolver}, {@link FallbackIgnoreURIResolver} and {@link FallbackIgnoreXMLResolver}) shares two defining properties:
36   * </p>
37   * <ol>
38   * <li><strong>Non-removable, and it wraps the resolver the caller sets.</strong> The secure wrappers install one and route a caller-set resolver through
39   * {@code setDelegate} rather than letting it replace the floor, so the caller's resolver is consulted first but cannot remove the floor underneath it.</li>
40   * <li><strong>It supplies the default action for a lookup the caller's resolver does not resolve</strong> (a {@code null} return, or no caller resolver at
41   * all). This is where a floor departs from stock JAXP: normally an unresolved lookup falls back to the processor's built-in resolution and the resource is
42   * <em>fetched</em>; a floor instead resolves it to <em>empty</em> content, so the parse continues without the external fetch and without a leak.</li>
43   * </ol>
44   * <p>
45   * The secure DOM and SAX wrappers install one of these and, when the caller sets their own {@link EntityResolver}, route it through {@link #setDelegate}
46   * rather than letting it replace the floor. A caller therefore opts a specific resource in by returning a non-{@code null} {@link InputSource} from their
47   * resolver; anything they leave unresolved (a {@code null} return, or no caller resolver at all) goes to {@link #onUnresolved}, which returns empty content by
48   * default.
49   * </p>
50   * <p>
51   * It extends {@link DefaultHandler2} so it is also usable as a {@link org.xml.sax.ext.LexicalHandler}; {@link #getExternalSubset} therefore inherits the
52   * {@code DefaultHandler2} "no synthetic subset" default. Only {@link #resolveEntity(String, String, String, String) resolveEntity} (the actual external fetch)
53   * reaches the ignore fallback.
54   * </p>
55   */
56  class FallbackIgnoreEntityResolver2 extends DefaultHandler2 {
57  
58      private static final byte[] EMPTY = {};
59  
60      /**
61       * Resolves {@code systemId} against {@code baseURI}.
62       *
63       * @param baseURI  The absolute base URI to resolve against, or {@code null} if none is available.
64       * @param systemId The system identifier, possibly relative to {@code baseURI}.
65       * @return The absolutized system identifier, or {@code systemId} unchanged when it cannot or need not be resolved.
66       */
67      private static String absolutize(final String baseURI, final String systemId) {
68          if (systemId == null || baseURI == null) {
69              return systemId;
70          }
71          try {
72              final URI system = new URI(systemId);
73              return system.isAbsolute() ? systemId : new URI(baseURI).resolve(system).toString();
74          } catch (final URISyntaxException e) {
75              return systemId;
76          }
77      }
78  
79      /**
80       * Caller-supplied resolver consulted first, or {@code null} for a pure ignore-all floor.
81       */
82      private EntityResolver delegate;
83  
84      /**
85       * Constructs a new ignore-all floor with an optional caller-supplied resolver.
86       *
87       * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
88       */
89      FallbackIgnoreEntityResolver2(final EntityResolver delegate) {
90          this.delegate = delegate;
91      }
92  
93      /**
94       * Gets the delegate provided by the constructor or set by {@link #setDelegate}, may be {@code null}.
95       *
96       * @return The delegate provided by the constructor or set by {@link #setDelegate}, may be {@code null}.
97       */
98      final EntityResolver getDelegate() {
99          return delegate;
100     }
101 
102     /**
103      * Outcome when neither the caller delegate nor this resolver provides the entity. Resolves to empty content by default, so the external resource is neither
104      * fetched nor leaked and the parse continues with no replacement text. The returned source echoes the requested identifiers (with {@code systemId}
105      * absolutized): the parser reads the empty byte stream, but Xerces still derives the entity's base URI from the system id and fails on a {@code null} one.
106      *
107      * @param name     The entity name, or {@code null} on the 2-arg resolution path.
108      * @param publicId The public identifier, or {@code null} if none.
109      * @param baseURI  The base URI for relative resolution, or {@code null}.
110      * @param systemId The system identifier of the unresolved entity.
111      * @return An empty {@link InputSource} carrying the requested identifiers.
112      * @throws SAXException when {@value SecureException#THROW_ON_UNRESOLVED} is set: unresolved references are rejected instead of resolved to empty.
113      * @throws IOException  never by the default implementation.
114      */
115     protected InputSource onUnresolved(final String name, final String publicId, final String baseURI, final String systemId) throws SAXException, IOException {
116         if (SecureException.throwOnUnresolved()) {
117             throw new SAXException(SecureException.forbidden(name, null, publicId, systemId, baseURI));
118         }
119         final InputSource empty = new InputSource(new ByteArrayInputStream(EMPTY));
120         empty.setPublicId(publicId);
121         empty.setSystemId(absolutize(baseURI, systemId));
122         return empty;
123     }
124 
125     @Override
126     public final InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException {
127         return resolveEntity(null, publicId, null, systemId);
128     }
129 
130     @Override
131     public final InputSource resolveEntity(final String name, final String publicId, final String baseURI, final String systemId)
132             throws SAXException, IOException {
133         final InputSource resolved = resolveWithDelegate(name, publicId, baseURI, systemId);
134         return resolved != null ? resolved : onUnresolved(name, publicId, baseURI, systemId);
135     }
136 
137     private InputSource resolveWithDelegate(final String name, final String publicId, final String baseURI, final String systemId)
138             throws SAXException, IOException {
139         if (delegate != null) {
140             return delegate instanceof EntityResolver2 ? ((EntityResolver2) delegate).resolveEntity(name, publicId, baseURI, systemId) :
141             // We need to resolve the systemId against baseURI, because a plain EntityResolver expects an absolute URI.
142                     delegate.resolveEntity(publicId, absolutize(baseURI, systemId));
143         }
144         return null;
145     }
146 
147     /**
148      * Replaces the caller resolver consulted ahead of the floor; lets a single floor instance back successive {@code setEntityResolver} calls.
149      *
150      * @param delegate The caller-supplied resolver, or {@code null} for a pure ignore-all floor.
151      */
152     final void setDelegate(final EntityResolver delegate) {
153         this.delegate = delegate;
154     }
155 }