ParsingValidator.java
/*
This file is licensed to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
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 org.xmlunit.validation;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import org.xmlunit.ConfigurationException;
import org.xmlunit.XMLUnitException;
import org.xmlunit.util.Convert;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Validator implementation that uses "the old way" of validating an
* XML input by parsing the input.
*
* <p>Even though this implementation supports W3C Schema you
* shouldn't use it for that language but rather use
* JAXPValidator.</p>
*
* <p><strong>Security note:</strong> like the rest of the {@code
* validation} package this class does not disable external entities
* by default - that has been a conscious decision since XMLUnit 2.6.0.
* An instance document with a {@code DOCTYPE} that declares an external
* entity may therefore cause that entity to be resolved while it is
* validated. If you validate untrusted input use {@link
* #setDisableExternalEntities setDisableExternalEntities(true)} to
* forbid this.</p>
*/
public class ParsingValidator extends Validator {
private final String language;
private String schemaURI;
private boolean disableExternalEntities;
/**
* Creates a validator for the given schema language.
* @param language the schema language
*/
public ParsingValidator(String language) {
if (!Languages.W3C_XML_SCHEMA_NS_URI.equals(language)
&& !Languages.XML_DTD_NS_URI.equals(language)) {
throw new IllegalArgumentException("only DTD and W3C Schema"
+ " validation are supported by"
+ " ParsingValidator");
}
this.language = language;
}
/**
* The URI (or for example the System ID in case of a DTD) that
* identifies the schema to validate or use during validation.
* @param uri the schema URI
*/
public void setSchemaURI(String uri) {
this.schemaURI = uri;
}
/**
* The URI (or for example the System ID in case of a DTD) that
* identifies the schema validated or used during validation.
* @return the schema URI
*/
protected String getSchemaURI() {
return schemaURI;
}
/**
* Whether external general and parameter entities should be
* disabled when the instance document is parsed for validation.
*
* <p>The default is {@code false}, leaving external entities
* enabled as they have been since XMLUnit 2.6.0. Setting this to
* {@code true} turns off the {@code external-general-entities} and
* {@code external-parameter-entities} features on the parser, which
* closes the XXE vector when validating untrusted instances. The
* DTD or schema to validate against is still resolved through the
* internal entity resolver, so DTD and schema validation keep
* working.</p>
*
* @since XMLUnit 2.12.1
* @param disable whether to disable external entities
*/
public void setDisableExternalEntities(boolean disable) {
disableExternalEntities = disable;
}
/**
* {@link ParsingValidator} doesn't support validation of the
* schema itself.
* @throws XMLUnitException always
*/
@Override public ValidationResult validateSchema() {
throw new XMLUnitException("Schema validation is not supported by"
+ " ParsingValidator");
}
@Override
public ValidationResult validateInstance(Source s) {
return validateInstance(s, SAXParserFactory.newInstance());
}
/**
* Validates an instance against the schema using a pre-configured {@link SAXParserFactory}.
*
* <p>The factory given will be configured to be namespace aware and validating.</p>
*
* @param s the instance document
* @param factory the factory to use, must not be null
* @return result of the validation
*
* @since XMLUnit 2.6.0
*/
public ValidationResult validateInstance(Source s, SAXParserFactory factory) {
if (factory == null) {
throw new IllegalArgumentException("factory must not be null");
}
try {
factory.setNamespaceAware(true);
factory.setValidating(true);
if (disableExternalEntities) {
restrictExternalEntities(factory);
}
SAXParser parser = factory.newSAXParser();
if (Languages.W3C_XML_SCHEMA_NS_URI.equals(language)) {
parser.setProperty(Properties.SCHEMA_LANGUAGE,
Languages.W3C_XML_SCHEMA_NS_URI);
}
final Source[] source = getSchemaSources();
Handler handler = new Handler();
if (source.length != 0) {
if (Languages.W3C_XML_SCHEMA_NS_URI.equals(language)) {
InputSource[] schemaSource = new InputSource[source.length];
for (int i = 0; i < source.length; i++) {
schemaSource[i] = Convert.toInputSource(source[i]);
}
parser.setProperty(Properties.SCHEMA_SOURCE,
schemaSource);
} else if (source.length == 1) {
handler.setSchemaSystemId(source[0].getSystemId());
}
}
InputSource input = Convert.toInputSource(s);
try {
parser.parse(input, handler);
} catch (SAXParseException e) {
handler.error((SAXParseException) e);
} catch (SAXException e) {
throw new XMLUnitException(e);
}
return handler.getResult();
} catch (ParserConfigurationException ex) {
throw new ConfigurationException(ex);
} catch (SAXNotRecognizedException ex) {
throw new ConfigurationException(ex);
} catch (SAXNotSupportedException ex) {
throw new ConfigurationException(ex);
} catch (SAXException ex) {
throw new XMLUnitException(ex);
} catch (java.io.IOException ex) {
throw new XMLUnitException(ex);
}
}
private static final String EXTERNAL_GENERAL_ENTITIES =
"http://xml.org/sax/features/external-general-entities";
private static final String EXTERNAL_PARAMETER_ENTITIES =
"http://xml.org/sax/features/external-parameter-entities";
/**
* Stops the instance document from pulling in external general or
* parameter entities while it is parsed for validation.
*
* <p>The DTD or schema to validate against is still resolved
* through the {@link Handler}, only entities declared inside the
* instance are affected.</p>
*/
private static void restrictExternalEntities(SAXParserFactory factory) {
setSafeFeature(factory, EXTERNAL_GENERAL_ENTITIES, false);
setSafeFeature(factory, EXTERNAL_PARAMETER_ENTITIES, false);
}
private static void setSafeFeature(SAXParserFactory factory, String feature, boolean value) {
try {
factory.setFeature(feature, value);
} catch (ParserConfigurationException ex) {
// feature not supported by this parser, ignore
} catch (SAXException ex) {
// feature not supported by this parser, ignore
}
}
private static class Properties {
static final String SCHEMA_LANGUAGE =
"http://java.sun.com/xml/jaxp/properties/schemaLanguage";
static final String SCHEMA_SOURCE =
"http://java.sun.com/xml/jaxp/properties/schemaSource";
private Properties() {}
}
private class Handler extends DefaultHandler {
private final ValidationHandler v = new ValidationHandler();
private String systemId;
@Override public void error(SAXParseException e) {
v.error(e);
}
@Override public void fatalError(SAXParseException e) {
v.fatalError(e);
}
@Override public void warning(SAXParseException e) {
v.warning(e);
}
private void setSchemaSystemId(String id) {
systemId = id;
}
@Override public InputSource resolveEntity(String publicId,
String systemId)
throws java.io.IOException, SAXException {
if (this.systemId != null &&
(getSchemaURI() == null || getSchemaURI().equals(publicId))
) {
return new InputSource(this.systemId);
}
return super.resolveEntity(publicId, systemId);
}
ValidationResult getResult() {
return v.getResult();
}
}
}