BaseSchemaFactory.java
/* Woodstox XML processor
*
* Copyright (c) 2004- Tatu Saloranta, tatu.saloranta@iki.fi
*
* Licensed under the License specified in the file LICENSE which is
* included with the source code.
* You may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ctc.wstx.msv;
import java.io.*;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.stream.*;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.codehaus.stax2.validation.*;
import com.ctc.wstx.api.ValidatorConfig;
import com.ctc.wstx.api.WstxInputProperties;
import com.ctc.wstx.exc.WstxIOException;
import com.ctc.wstx.util.ArgUtil;
import com.ctc.wstx.util.URLUtil;
/**
* Shared base class extended by concrete schema factory implementations.
*/
public abstract class BaseSchemaFactory
extends XMLValidationSchemaFactory
{
/**
* Shared factory for the default (secure) case, where external entities
* referenced from schema documents are not resolved.
*/
protected static SAXParserFactory sSaxFactory;
/**
* Shared factory for the opt-in case, where external entities are resolved
* (legacy behaviour, see {@link WstxInputProperties#P_MSV_SCHEMA_EXTERNAL_ACCESS}).
*/
protected static SAXParserFactory sSaxFactoryExternalAccess;
/**
* Current configurations for this factory
*/
protected final ValidatorConfig mConfig;
/**
* Whether external entities and the external DTD subset referenced from a
* schema document may be resolved while loading it; {@code false} by
* default (see {@link WstxInputProperties#P_MSV_SCHEMA_EXTERNAL_ACCESS}).
*/
protected boolean mAllowExternalAccess = false;
protected BaseSchemaFactory(String schemaType)
{
super(schemaType);
mConfig = ValidatorConfig.createDefaults();
}
/*
////////////////////////////////////////////////////////////
// Stax2, Configuration methods
////////////////////////////////////////////////////////////
*/
@Override
public boolean isPropertySupported(String propName) {
if (WstxInputProperties.P_MSV_SCHEMA_EXTERNAL_ACCESS.equals(propName)) {
return true;
}
return mConfig.isPropertySupported(propName);
}
@Override
public boolean setProperty(String propName, Object value) {
if (WstxInputProperties.P_MSV_SCHEMA_EXTERNAL_ACCESS.equals(propName)) {
mAllowExternalAccess = ArgUtil.convertToBoolean(propName, value);
return true;
}
return mConfig.setProperty(propName, value);
}
@Override
public Object getProperty(String propName) {
if (WstxInputProperties.P_MSV_SCHEMA_EXTERNAL_ACCESS.equals(propName)) {
return mAllowExternalAccess ? Boolean.TRUE : Boolean.FALSE;
}
return mConfig.getProperty(propName);
}
/*
////////////////////////////////////////////////////////////
// Stax2, Factory methods
////////////////////////////////////////////////////////////
*/
@Override
public XMLValidationSchema createSchema(InputStream in, String encoding,
String publicId, String systemId)
throws XMLStreamException
{
InputSource src = new InputSource(in);
src.setEncoding(encoding);
src.setPublicId(publicId);
src.setSystemId(systemId);
return loadSchema(src, systemId);
}
@Override
public XMLValidationSchema createSchema(Reader r, String publicId,
String systemId)
throws XMLStreamException
{
InputSource src = new InputSource(r);
src.setPublicId(publicId);
src.setSystemId(systemId);
return loadSchema(src, systemId);
}
@SuppressWarnings("resource")
@Override
public XMLValidationSchema createSchema(URL url)
throws XMLStreamException
{
try {
InputStream in = URLUtil.inputStreamFromURL(url);
InputSource src = new InputSource(in);
src.setSystemId(url.toExternalForm());
return loadSchema(src, url);
} catch (IOException ioe) {
throw new WstxIOException(ioe);
}
}
@SuppressWarnings("deprecation")
@Override
public XMLValidationSchema createSchema(File f)
throws XMLStreamException
{
try {
return createSchema(f.toURL());
} catch (IOException ioe) {
throw new WstxIOException(ioe);
}
}
/*
////////////////////////////////////////////////////////////
// Methods sub-classes need to implement
////////////////////////////////////////////////////////////
*/
protected abstract XMLValidationSchema loadSchema(InputSource src, Object sysRef)
throws XMLStreamException;
/*
////////////////////////////////////////////////////////////
// Internal/package methods
////////////////////////////////////////////////////////////
*/
/**
* Legacy accessor, retained for backwards compatibility with sub-classes;
* returns the secure (no external entity access) factory.
*
* @deprecated Since 7.3.0 use {@link #getSaxFactory(boolean)}
*/
@Deprecated
protected static SAXParserFactory getSaxFactory() {
return getSaxFactory(false);
}
/**
* We will essentially share a singleton sax parser factory (one per
* external-access setting); the reason being that constructing (or, rather,
* locating implementation class) is bit expensive.
*
* @param allowExternalAccess Whether the returned factory may resolve
* external entities referenced from schema documents
*
* @throws IllegalStateException If external entity resolution cannot be
* disabled for the SAX implementation in use (only when
* {@code allowExternalAccess} is {@code false})
*/
protected synchronized static SAXParserFactory getSaxFactory(boolean allowExternalAccess)
{
if (allowExternalAccess) {
if (sSaxFactoryExternalAccess == null) {
SAXParserFactory f = SAXParserFactory.newInstance();
f.setNamespaceAware(true);
sSaxFactoryExternalAccess = f;
}
return sSaxFactoryExternalAccess;
}
if (sSaxFactory == null) {
SAXParserFactory f = SAXParserFactory.newInstance();
f.setNamespaceAware(true);
disableExternalEntities(f);
sSaxFactory = f;
}
return sSaxFactory;
}
/**
* Variant of {@link #getSaxFactory(boolean)} for schema loading methods,
* which are declared to throw {@link XMLStreamException}: failure to
* configure the factory is reported as one rather than as an unchecked
* exception.
*/
static SAXParserFactory getSaxFactoryForLoading(boolean allowExternalAccess)
throws XMLStreamException
{
try {
return getSaxFactory(allowExternalAccess);
} catch (IllegalStateException e) {
throw new XMLStreamException(e.getMessage(), e);
}
}
/**
* Method for disabling resolution of external entities referenced from
* schema documents. Neither this factory nor MSV exposes the underlying
* SAX parser, so callers have no way of doing this themselves.
*<p>
* External general and parameter entities are switched off, and the
* external DTD subset is left unread (rather than made a fatal error), so
* a schema that declares a {@code DOCTYPE} pointing at an external DTD but
* uses no entities still loads.
*<p>
* Does not affect {@code xs:include} / {@code xs:import} or RELAX NG
* {@code externalRef}: those are resolved by MSV itself, not through
* entity resolution.
*
* @throws IllegalStateException If the SAX implementation does not support
* disabling external general or parameter entities: failing silently
* would leave schema loading open to XXE while claiming otherwise.
*/
static void disableExternalEntities(SAXParserFactory f)
{
setRequiredFeature(f, "http://xml.org/sax/features/external-general-entities", false);
setRequiredFeature(f, "http://xml.org/sax/features/external-parameter-entities", false);
// Skip (rather than fail on) the external DTD subset: keeps schemas
// whose DOCTYPE references an external DTD loadable, while still not
// reading its contents. Xerces-specific, hence optional (SAX defines
// 'external-parameter-entities' as also covering the external DTD subset).
try {
f.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (ParserConfigurationException | SAXNotRecognizedException | SAXNotSupportedException e) {
// Not every SAX implementation knows this feature; nothing to do
}
}
private static void setRequiredFeature(SAXParserFactory f, String feature, boolean state)
{
try {
f.setFeature(feature, state);
} catch (ParserConfigurationException | SAXNotRecognizedException | SAXNotSupportedException e) {
throw new IllegalStateException("SAX parser factory ("+f.getClass().getName()
+") does not support feature '"+feature+"', needed to disable external entity"
+" resolution for schema loading; either use a SAX implementation that supports it,"
+" or explicitly allow external access with property '"
+WstxInputProperties.P_MSV_SCHEMA_EXTERNAL_ACCESS+"'", e);
}
}
/*
////////////////////////////////////////////////////////////
// Helper classes
////////////////////////////////////////////////////////////
*/
final static class MyGrammarController
extends com.sun.msv.reader.util.IgnoreController
{
public String mErrorMsg = null;
public MyGrammarController() { }
//public void warning(Locator[] locs, String errorMessage) { }
@Override
public void error(Locator[] locs, String msg, Exception nestedException )
{
if (mErrorMsg == null) {
mErrorMsg = msg;
} else {
mErrorMsg = mErrorMsg + "; " + msg;
}
}
}
}