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}