Skip to content

Commit 5da0c68

Browse files
committed
Automatic detection of schema version (if defined in root schema)
1 parent 14d79ac commit 5da0c68

7 files changed

Lines changed: 103 additions & 26 deletions

File tree

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/DomainConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,9 @@ public void setMailInboundFolder(String mailInboundFolder) {
475475
*/
476476
public XmlSchemaVersion getSchemaVersionForValidationType(String validationType) {
477477
if (validationType != null) {
478-
return schemaVersion.getOrDefault(validationType, XmlSchemaVersion.VERSION_1_0);
478+
return schemaVersion.get(validationType);
479479
}
480-
return XmlSchemaVersion.VERSION_1_0;
480+
return null;
481481
}
482482

483483
/**

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/DomainConfigCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ protected void addDomainConfiguration(DomainConfig domainConfig, Configuration c
122122
}
123123
// Local mappings for remote schema imports and caching - end
124124
// XML Schema version - start
125-
domainConfig.setSchemaVersion(ParseUtils.parseEnumMap("validator.schemaVersion", XmlSchemaVersion.from(config.getString("validator.schemaVersion", "1.0")), config, domainConfig.getType(), XmlSchemaVersion::from));
125+
domainConfig.setSchemaVersion(ParseUtils.parseEnumMap("validator.schemaVersion", XmlSchemaVersion.from(config.getString("validator.schemaVersion", null)), config, domainConfig.getType(), XmlSchemaVersion::from));
126126
// XML Schema version - end
127127
}
128128

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/ValidationSpecs.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -562,13 +562,10 @@ private void validateContextFiles() throws ValidatorException {
562562
LOG.info("Validating context file against [{}]", schemaFile.getName());
563563
// Validate XML content against given XSD schema.
564564
var errorHandler = new XSDReportHandler();
565-
try (
566-
var inputStream = Files.newInputStream(file.file());
567-
var schemaStream = Files.newInputStream(schemaFile.toPath())
568-
) {
565+
try (var inputStream = Files.newInputStream(file.file())) {
569566
secureSchemaValidation(
570567
inputStream,
571-
schemaStream,
568+
schemaFile.toPath(),
572569
errorHandler,
573570
applicationContext.getBean(XSDFileResolver.class, getDomainConfig(), schemaFile.toURI()),
574571
getLocalisationHelper().getLocale(),

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/XmlSchemaVersion.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,11 @@ public static XmlSchemaVersion from(String value) {
5050
return VERSION_1_0;
5151
} else if (VERSION_1_1.stringValue.equals(value)) {
5252
return VERSION_1_1;
53+
} else if (value == null) {
54+
return null;
5355
} else {
54-
LOG.warn("Invalid XML Schema version [{}] (expected '{}' or '{}'. Considering '{}' by default.", value, VERSION_1_0.stringValue, VERSION_1_1.stringValue, VERSION_1_0.stringValue);
55-
return VERSION_1_0;
56+
LOG.warn("Invalid XML Schema version [{}] (expected '{}' or '{}').", value, VERSION_1_0.stringValue, VERSION_1_1.stringValue);
57+
return null;
5658
}
5759
}
5860

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/util/Utils.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,21 @@
2626
import org.xml.sax.SAXNotRecognizedException;
2727
import org.xml.sax.SAXNotSupportedException;
2828

29+
import javax.annotation.Nullable;
2930
import javax.xml.XMLConstants;
31+
import javax.xml.stream.XMLInputFactory;
32+
import javax.xml.stream.XMLStreamConstants;
3033
import javax.xml.stream.XMLStreamException;
34+
import javax.xml.stream.XMLStreamReader;
3135
import javax.xml.transform.stax.StAXSource;
3236
import javax.xml.transform.stream.StreamSource;
3337
import javax.xml.validation.Schema;
3438
import javax.xml.validation.SchemaFactory;
3539
import javax.xml.validation.Validator;
3640
import java.io.IOException;
3741
import java.io.InputStream;
42+
import java.nio.file.Files;
43+
import java.nio.file.Path;
3844
import java.util.Locale;
3945

4046
import static eu.europa.ec.itb.validation.commons.Utils.secureXMLInputFactory;
@@ -63,7 +69,7 @@ private Utils() {}
6369
* @throws XMLStreamException If the input cannot be parsed as XML.
6470
* @throws SAXException If the input is invalid (not thrown for regular errors if a custom errorHandler is provided).
6571
*/
66-
public static void secureSchemaValidation(InputStream inputToValidate, InputStream schemaToValidateWith, ErrorHandler errorHandler, LSResourceResolver resourceResolver, Locale locale, XmlSchemaVersion schemaVersion) throws XMLStreamException, SAXException {
72+
public static void secureSchemaValidation(InputStream inputToValidate, Path schemaToValidateWith, ErrorHandler errorHandler, LSResourceResolver resourceResolver, Locale locale, @Nullable XmlSchemaVersion schemaVersion) throws XMLStreamException, SAXException {
6773
/*
6874
* We create specifically a Xerces parser to allow localisation of output messages.
6975
* The security configuration for the Xerces parser involves:
@@ -72,16 +78,19 @@ public static void secureSchemaValidation(InputStream inputToValidate, InputStre
7278
* Xerces does not directly support the JAXP 1.5 features to disable XXE (ACCESS_EXTERNAL_DTD, ACCESS_EXTERNAL_SCHEMA)
7379
* but we ensure secure processing by means of the secured underlying parser.
7480
*/
75-
SchemaFactory factory = schemaVersion == XmlSchemaVersion.VERSION_1_1?new XMLSchema11Factory():new XMLSchemaFactory();
81+
XmlSchemaVersion schemaVersionToUse = (schemaVersion == null)?parseSchemaVersion(schemaToValidateWith):schemaVersion;
82+
SchemaFactory factory = schemaVersionToUse == XmlSchemaVersion.VERSION_1_1?new XMLSchema11Factory():new XMLSchemaFactory();
7683
if (errorHandler != null) factory.setErrorHandler(errorHandler);
7784
if (resourceResolver != null) factory.setResourceResolver(resourceResolver);
7885
Schema schema;
79-
try {
86+
try (var schemaStream = Files.newInputStream(schemaToValidateWith)) {
8087
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
8188
factory.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.SCHEMA_FULL_CHECKING, true);
82-
schema = factory.newSchema(new StreamSource(schemaToValidateWith));
89+
schema = factory.newSchema(new StreamSource(schemaStream));
8390
} catch (SAXException e) {
8491
throw new IllegalStateException("Unable to configure schema", e);
92+
} catch (IOException e) {
93+
throw new IllegalStateException("IO error while configuring schema", e);
8594
}
8695
Validator validator = schema.newValidator();
8796
try {
@@ -99,4 +108,32 @@ public static void secureSchemaValidation(InputStream inputToValidate, InputStre
99108
}
100109
}
101110

111+
/**
112+
* Parse the XML Schema version from the schema.
113+
*
114+
* @param schemaToValidateWith The schema to parse.
115+
* @return The XML Schema version (defaults to 1.0 if not defined).
116+
*/
117+
private static XmlSchemaVersion parseSchemaVersion(Path schemaToValidateWith) {
118+
try (var stream = Files.newInputStream(schemaToValidateWith)) {
119+
XMLInputFactory factory = XMLInputFactory.newInstance();
120+
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
121+
factory.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
122+
XMLStreamReader reader = factory.createXMLStreamReader(stream);
123+
// Advance to the root element
124+
while (reader.hasNext()) {
125+
int event = reader.next();
126+
if (event == XMLStreamConstants.START_ELEMENT) {
127+
String minVersion = reader.getAttributeValue("http://www.w3.org/2007/XMLSchema-versioning","minVersion");
128+
return "1.1".equals(minVersion) ? XmlSchemaVersion.VERSION_1_1 : XmlSchemaVersion.VERSION_1_0;
129+
}
130+
}
131+
return XmlSchemaVersion.VERSION_1_0;
132+
} catch (XMLStreamException e) {
133+
throw new RuntimeException("Failed to parse schema version from schema file", e);
134+
} catch (IOException e) {
135+
throw new RuntimeException("IO failure when parsing schema version from schema file", e);
136+
}
137+
}
138+
102139
}

xmlvalidator-common/src/main/java/eu/europa/ec/itb/xml/validation/XMLValidator.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,8 @@ private TAR validateAgainstSchema() throws XMLInvalidException {
142142
List<TAR> reports = new ArrayList<>();
143143
for (FileInfo aSchemaFile: schemaFiles) {
144144
if (specs.isLogProgress()) {
145-
logger.info("Validating against [{}] (XML Schema {})", aSchemaFile.getFile().getName(), specs.getDomainConfig().getSchemaVersionForValidationType(specs.getValidationType()));
145+
XmlSchemaVersion version = specs.getDomainConfig().getSchemaVersionForValidationType(specs.getValidationType());
146+
logger.info("Validating against [{}]{}", aSchemaFile.getFile().getName(), (version == null)?"":" (XML Schema %s)".formatted(version));
146147
}
147148
TAR report = validateSchema(specs.getInputStreamForValidation(false), aSchemaFile);
148149
logReport(report, aSchemaFile.getFile().getName());
@@ -167,8 +168,8 @@ private TAR validateAgainstSchema() throws XMLInvalidException {
167168
private TAR validateSchema(InputStream inputStream, FileInfo schemaFile) throws XMLInvalidException {
168169
// Validate XML content against given XSD schema.
169170
var errorHandler = new XSDReportHandler();
170-
try (var schemaStream = Files.newInputStream(schemaFile.getFile().toPath())) {
171-
secureSchemaValidation(inputStream, schemaStream, errorHandler, getXSDResolver(schemaFile.getSource()), specs.getLocalisationHelper().getLocale(), specs.getDomainConfig().getSchemaVersionForValidationType(getValidationType()));
171+
try {
172+
secureSchemaValidation(inputStream, schemaFile.getFile().toPath(), errorHandler, getXSDResolver(schemaFile.getSource()), specs.getLocalisationHelper().getLocale(), specs.getDomainConfig().getSchemaVersionForValidationType(getValidationType()));
172173
} catch (Exception e) {
173174
throw new XMLInvalidException(e);
174175
}

xmlvalidator-common/src/test/java/eu/europa/ec/itb/xml/util/UtilsTest.java

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,36 @@
22

33
import eu.europa.ec.itb.xml.XmlSchemaVersion;
44
import org.junit.jupiter.api.Test;
5+
import org.junit.jupiter.api.io.TempDir;
56
import org.xml.sax.ErrorHandler;
67
import org.xml.sax.SAXException;
78
import org.xml.sax.SAXParseException;
89

10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.util.Objects;
13+
import java.util.UUID;
14+
915
import static eu.europa.ec.itb.xml.util.Utils.secureSchemaValidation;
1016
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
1117
import static org.junit.jupiter.api.Assertions.assertThrows;
1218
import static org.mockito.Mockito.*;
1319

1420
class UtilsTest {
1521

22+
@TempDir
23+
Path tempDirectory;
24+
1625
@Test
1726
void testSchemaValidationValid() {
1827
assertDoesNotThrow(() -> {
1928
try (
2029
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/valid.xml");
2130
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
2231
) {
23-
secureSchemaValidation(inputStream, schemaStream, null, null, null, XmlSchemaVersion.VERSION_1_0);
32+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
33+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
34+
secureSchemaValidation(inputStream, schemaPath, null, null, null, XmlSchemaVersion.VERSION_1_0);
2435
}
2536
});
2637
}
@@ -33,7 +44,9 @@ void testSchemaValidationInvalidXSD() throws SAXException {
3344
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xsd.xml");
3445
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
3546
) {
36-
secureSchemaValidation(inputStream, schemaStream, null, null, null, XmlSchemaVersion.VERSION_1_0);
47+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
48+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
49+
secureSchemaValidation(inputStream, schemaPath, null, null, null, XmlSchemaVersion.VERSION_1_0);
3750
}
3851
});
3952
// With error handler.
@@ -43,7 +56,9 @@ void testSchemaValidationInvalidXSD() throws SAXException {
4356
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xsd.xml");
4457
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
4558
) {
46-
secureSchemaValidation(inputStream, schemaStream, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
59+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
60+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
61+
secureSchemaValidation(inputStream, schemaPath, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
4762
}
4863
});
4964
verify(errorHandler, atLeastOnce()).error(any(SAXParseException.class));
@@ -57,7 +72,9 @@ void testSchemaValidationInvalidXML() throws SAXException {
5772
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xml.xml");
5873
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
5974
) {
60-
secureSchemaValidation(inputStream, schemaStream, null, null, null, XmlSchemaVersion.VERSION_1_0);
75+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
76+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
77+
secureSchemaValidation(inputStream, schemaPath, null, null, null, XmlSchemaVersion.VERSION_1_0);
6178
}
6279
});
6380
// With error handler.
@@ -67,7 +84,9 @@ void testSchemaValidationInvalidXML() throws SAXException {
6784
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xml.txt");
6885
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
6986
) {
70-
secureSchemaValidation(inputStream, schemaStream, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
87+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
88+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
89+
secureSchemaValidation(inputStream, schemaPath, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
7190
}
7291
});
7392
verify(errorHandler, atLeastOnce()).error(any(SAXParseException.class));
@@ -81,7 +100,9 @@ void testSchemaValidationMissingXML() throws SAXException {
81100
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/missing.xml");
82101
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
83102
) {
84-
secureSchemaValidation(inputStream, schemaStream, null, null, null, XmlSchemaVersion.VERSION_1_0);
103+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
104+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
105+
secureSchemaValidation(inputStream, schemaPath, null, null, null, XmlSchemaVersion.VERSION_1_0);
85106
}
86107
});
87108
// With error handler.
@@ -91,7 +112,9 @@ void testSchemaValidationMissingXML() throws SAXException {
91112
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/missing.txt");
92113
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
93114
) {
94-
secureSchemaValidation(inputStream, schemaStream, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
115+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
116+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
117+
secureSchemaValidation(inputStream, schemaPath, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
95118
}
96119
});
97120
verify(errorHandler, never()).error(any());
@@ -105,7 +128,9 @@ void testSchemaValidationInvalidXXE() throws SAXException {
105128
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xxe.xml");
106129
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
107130
) {
108-
secureSchemaValidation(inputStream, schemaStream, null, null, null, XmlSchemaVersion.VERSION_1_0);
131+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
132+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
133+
secureSchemaValidation(inputStream, schemaPath, null, null, null, XmlSchemaVersion.VERSION_1_0);
109134
}
110135
});
111136
// With error handler.
@@ -115,10 +140,25 @@ void testSchemaValidationInvalidXXE() throws SAXException {
115140
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/invalid_xxe.xml");
116141
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
117142
) {
118-
secureSchemaValidation(inputStream, schemaStream, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
143+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
144+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
145+
secureSchemaValidation(inputStream, schemaPath, errorHandler, null, null, XmlSchemaVersion.VERSION_1_0);
119146
}
120147
});
121148
verify(errorHandler, never()).error(any());
122149
}
123150

151+
@Test
152+
void testSchemaValidationWithVersionDetection() {
153+
assertDoesNotThrow(() -> {
154+
try (
155+
var inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/testFiles/valid.xml");
156+
var schemaStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("utils/PurchaseOrder.xsd")
157+
) {
158+
Path schemaPath = tempDirectory.resolve(UUID.randomUUID() +".xsd");
159+
Files.copy(Objects.requireNonNull(schemaStream), schemaPath);
160+
secureSchemaValidation(inputStream, schemaPath, null, null, null, null);
161+
}
162+
});
163+
}
124164
}

0 commit comments

Comments
 (0)