001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.xml.secure;
019
020import java.lang.invoke.MethodHandle;
021import java.lang.invoke.MethodType;
022import java.util.Objects;
023
024import javax.xml.XMLConstants;
025import javax.xml.xpath.XPath;
026import javax.xml.xpath.XPathFactory;
027import javax.xml.xpath.XPathFactoryConfigurationException;
028import javax.xml.xpath.XPathFunctionResolver;
029import javax.xml.xpath.XPathVariableResolver;
030
031/**
032 * Creates new, secure {@link XPathFactory} instances.
033 * <p>
034 * Beyond the three universal guarantees on {@link org.apache.commons.xml.secure}, URI-fetching XPath 3.1+ functions ({@code doc()}, {@code collection()},
035 * {@code unparsed-text()}) are not resolved.
036 * </p>
037 * <p>
038 * The guarantees also cover the document parse behind {@code XPath.evaluate(String, InputSource)} and {@code XPathExpression.evaluate(InputSource)}: the
039 * input document is built through a secure, namespace-aware {@link javax.xml.parsers.DocumentBuilder} instead of the engine's internal parser.
040 * </p>
041 * <p>
042 * Not a {@link XPathFactory} itself, so none of the JAXP static factory methods is inherited: a caller cannot reach a non-secured factory through this class
043 * by calling an inherited method such as {@code newDefaultInstance()}. The secure factories are instances of a nested, non-public wrapper class.
044 * </p>
045 *
046 * @see org.apache.commons.xml.secure
047 */
048public final class SecureXPathFactory {
049
050    /**
051     * {@link XPathFactory} wrapper that returns a {@link SecureXPath} from {@link #newXPath()}.
052     *
053     * <p>Required because {@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING} on the factory governs only the XPath engine: the stock JDK and Apache Xalan
054     * implement the {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points by provisioning an internal document parser the feature does not reach.
055     * The wrapper performs that document build itself through a secure parser instead; see {@link SecureXPath}.</p>
056     *
057     * @see org.apache.commons.xml.secure
058     */
059    private static final class Wrapper extends XPathFactory {
060
061        private final XPathFactory delegate;
062
063        /**
064         * Constructs a new instance.
065         *
066         * @param delegate the delegate to wrap; must not be {@code null}.
067         * @throws NullPointerException if {@code delegate} is {@code null}.
068         */
069        private Wrapper(final XPathFactory delegate) {
070            this.delegate = Objects.requireNonNull(delegate, "delegate");
071        }
072
073        @Override
074        public boolean getFeature(final String name) throws XPathFactoryConfigurationException {
075            return delegate.getFeature(name);
076        }
077
078        @Override
079        public boolean isObjectModelSupported(final String objectModel) {
080            return delegate.isObjectModelSupported(objectModel);
081        }
082
083        @Override
084        public XPath newXPath() {
085            final XPath xpath = delegate.newXPath();
086            return xpath == null ? null : new SecureXPath(xpath, overrideDefaultParser());
087        }
088
089        /**
090         * Checks whether parsers should be instantiated via {@code newInstance()} instead of {@code newDefaultInstance()}.
091         *
092         * <p>The JDK implementation of {@link XPathFactory} uses the JDK parsers while {@value SecureSAXParserFactory#OVERRIDE_DEFAULT_PARSER} is unset or
093         * {@code false}.</p>
094         *
095         * @return {@code true} if parsers should be created via {@code newInstance()}.
096         */
097        private boolean overrideDefaultParser() {
098            try {
099                return delegate.getFeature(SecureSAXParserFactory.OVERRIDE_DEFAULT_PARSER);
100            } catch (final XPathFactoryConfigurationException e) {
101                return true;
102            }
103        }
104
105        @Override
106        public void setFeature(final String name, final boolean value) throws XPathFactoryConfigurationException {
107            delegate.setFeature(name, value);
108        }
109
110        @Override
111        public void setXPathFunctionResolver(final XPathFunctionResolver resolver) {
112            delegate.setXPathFunctionResolver(resolver);
113        }
114
115        @Override
116        public void setXPathVariableResolver(final XPathVariableResolver resolver) {
117            delegate.setXPathVariableResolver(resolver);
118        }
119    }
120
121    /** Class name of the JDK's built-in default implementation, the Java 8 fallback for {@link #newDefaultInstance()}. */
122    private static final String JDK_XPATH_FACTORY = "com.sun.org.apache.xpath.internal.jaxp.XPathFactoryImpl";
123
124    private static final MethodHandle MH_newDefaultInstance = MethodHandleFactory.findStatic(XPathFactory.class, "newDefaultInstance",
125            MethodType.methodType(XPathFactory.class));
126
127    /**
128     * Returns a new, secure {@link XPathFactory} of the system-default implementation, supporting the default XPath object model.
129     * <p>
130     * Obtained as by {@code XPathFactory.newDefaultInstance()} where the platform provides it (Java 9 or later), and by instantiating the JDK's built-in
131     * implementation directly on Java 8.
132     * </p>
133     *
134     * @return A secure factory.
135     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
136     * @throws RuntimeException      Thrown if the running platform provides neither {@code newDefaultInstance()} nor the JDK's built-in implementation (for
137     *                               example Android).
138     */
139    public static XPathFactory newDefaultInstance() {
140        if (MH_newDefaultInstance != null) {
141            return secure(MethodHandleFactory.invokeExact(() -> (XPathFactory) MH_newDefaultInstance.invokeExact(), RuntimeException.class));
142        }
143        try {
144            // Java 8: the method does not exist; instantiate the JDK's built-in default by its class name instead.
145            return newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI, JDK_XPATH_FACTORY, null);
146        } catch (final XPathFactoryConfigurationException e) {
147            // newDefaultInstance declares no checked exception; mirror XPathFactory.newInstance(), which reports a default-model miss as a RuntimeException.
148            throw new RuntimeException("Neither XPathFactory.newDefaultInstance() nor " + JDK_XPATH_FACTORY + " is available", e);
149        }
150    }
151
152    /**
153     * Returns a new, secure {@link XPathFactory} for the default XPath object model.
154     *
155     * @return A secure factory.
156     * @throws IllegalStateException Thrown if a required secure setting cannot be applied to the underlying implementation.
157     * @throws RuntimeException      Thrown if there is a failure in creating an {@link XPathFactory} for the default object model.
158     */
159    public static XPathFactory newInstance() {
160        return secure(XPathFactory.newInstance());
161    }
162
163    /**
164     * Returns a new, secure {@link XPathFactory} for the given object model.
165     *
166     * @param uri The underlying object model identifier, as accepted by {@link XPathFactory#newInstance(String)}.
167     * @return A secure factory.
168     * @throws IllegalStateException              Thrown if a required secure setting cannot be applied to the underlying implementation.
169     * @throws XPathFactoryConfigurationException Thrown if no implementation of the object model is available.
170     * @throws NullPointerException               Thrown if {@code uri} is {@code null}.
171     * @throws IllegalArgumentException           Thrown if {@code uri} is empty.
172     */
173    public static XPathFactory newInstance(final String uri) throws XPathFactoryConfigurationException {
174        return secure(XPathFactory.newInstance(uri));
175    }
176
177    /**
178     * Returns a new, secure {@link XPathFactory} of the given implementation class.
179     *
180     * @param uri              The underlying object model identifier, as accepted by {@link XPathFactory#newInstance(String)}.
181     * @param factoryClassName The fully qualified class name of the {@link XPathFactory} implementation.
182     * @param classLoader      The class loader used to load the factory class; {@code null} means the current thread's context class loader.
183     * @return A secure factory.
184     * @throws IllegalStateException              Thrown if a required secure setting cannot be applied to the underlying implementation.
185     * @throws XPathFactoryConfigurationException Thrown if {@code factoryClassName} is {@code null}, or if the factory class cannot be loaded or
186     *                                            instantiated, or does not support {@code uri}.
187     * @throws NullPointerException               Thrown if {@code uri} is {@code null}.
188     * @throws IllegalArgumentException           Thrown if {@code uri} is empty.
189     */
190    public static XPathFactory newInstance(final String uri, final String factoryClassName, final ClassLoader classLoader)
191            throws XPathFactoryConfigurationException {
192        return secure(XPathFactory.newInstance(uri, factoryClassName, classLoader));
193    }
194
195    /**
196     * Capability-driven securing for any {@link XPathFactory} on the classpath.
197     *
198     * <p>The XPath object model mirrors TrAX: the stock JDK and Apache Xalan ship an XPath 1.0 engine with no URI-fetching functions, while Saxon adds the XPath 3.1
199     * {@code fn:doc}, {@code fn:collection} and {@code fn:unparsed-text} functions that can reach external resources. Rather than branching on the implementation
200     * class, this method probes what the factory supports and adapts:</p>
201     * <ul>
202     *     <li><strong>Saxon</strong> ({@code net.sf.saxon}): recognized by package prefix and handed to {@link SaxonProvider#configure(XPathFactory)}, so any public
203     *         subclass routes to the same recipe as the registered factory. Its URI-fetching
204     *         functions and reflection-based extension calls are reachable only through a locked-down Saxon {@code Configuration}, not the standard JAXP knobs; this
205     *         is the XPath counterpart of the Saxon exception in {@link SecureTransformerFactory#secure(javax.xml.transform.TransformerFactory)}, kept as a
206     *         documented package-prefix exception because the required securing surface is reachable only through a vendor API.</li>
207     *     <li><strong>FSP</strong> ({@link javax.xml.XMLConstants#FEATURE_SECURE_PROCESSING}): required. It is the only knob both the stock JDK and Xalan XPath
208     *         engines expose, and switches on their secure-processing limits. {@link XPathFactory} has no attribute API for finer control.</li>
209     *     <li><strong>The nested wrapper</strong>: required. FSP governs only the engine, not the parser it provisions internally for the
210     *         {@link org.xml.sax.InputSource}-taking {@code evaluate} entry points; the wrapper performs that document build with a secure parser instead, so
211     *         the engine never parses.</li>
212     * </ul>
213     *
214     * @param factory The factory to secure.
215     * @return A new secure factory or the original factory, secure, if it is a known Saxon factory.
216     * @throws SecureException Thrown if this {@link XPathFactory} or the {@code XPath}s it creates cannot support this feature.
217     */
218    static XPathFactory secure(final XPathFactory factory) {
219        if (SaxonProvider.isSaxon(factory.getClass())) {
220            // Saxon: only a locked-down Configuration can close its URI-fetching functions and extension-function surface.
221            return SaxonProvider.configure(factory);
222        }
223        // Required: enables the engine's secure-processing limits; XPathFactory has no attribute API for finer control.
224        setFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
225        // Required: FSP does not reach the parser the engine provisions for InputSource-taking evaluate calls; the wrapper parses those itself.
226        return new Wrapper(factory);
227    }
228
229    /**
230     * Sets a feature on the given factory, throwing a {@link SecureException} if the implementation does not recognize it.
231     *
232     * @param factory The factory to secure.
233     * @param feature The feature to set.
234     * @param value   The value to set.
235     * @throws SecureException Thrown if this {@link XPathFactory} or the {@code XPath}s it creates cannot support this feature or if {@code feature} is
236     *                            {@code null}.
237     */
238    private static void setFeature(final XPathFactory factory, final String feature, final boolean value) {
239        try {
240            factory.setFeature(feature, value);
241        } catch (final XPathFactoryConfigurationException e) {
242            throw SecureException.featureFailed(feature, factory, e);
243        }
244    }
245
246    private SecureXPathFactory() {
247        // static only
248    }
249}