Skip to content

Commit c85ba76

Browse files
committed
Use secure parser for reading xml schemas
1 parent b4f0524 commit c85ba76

File tree

8 files changed

+192
-120
lines changed

8 files changed

+192
-120
lines changed

jaxb/src/main/java/no/digipost/signature/jaxb/JaxbMarshaller.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import no.digipost.signature.xsd.SignatureApiSchemas;
1919
import no.digipost.xml.bind.MarshallingCustomization;
20+
import no.digipost.xml.parsers.SaxParserProvider;
2021
import org.w3c.dom.Document;
2122
import org.w3c.dom.Node;
2223
import org.xml.sax.InputSource;
@@ -26,7 +27,6 @@
2627
import javax.xml.bind.Unmarshaller;
2728
import javax.xml.transform.Source;
2829
import javax.xml.transform.dom.DOMResult;
29-
import javax.xml.transform.sax.SAXSource;
3030

3131
import java.io.ByteArrayInputStream;
3232
import java.io.ByteArrayOutputStream;
@@ -118,7 +118,7 @@ public ForAllApis() {
118118
public JaxbMarshaller(MarshallingCustomization marshallingCustomization, Class<?> ... classesToBeBound) {
119119
this.jaxbContext = JaxbUtils.initContext(classesToBeBound);
120120
this.marshallingCustomization = marshallingCustomization;
121-
this.saxParserProvider = SaxParserProvider.createSecuredParserFactory();
121+
this.saxParserProvider = SaxParserProvider.createSecuredProvider();
122122
}
123123

124124
public JaxbMarshaller(MarshallingCustomization marshallingCustomization, Set<Class<?>> classesToBeBound) {
@@ -194,7 +194,7 @@ private <T> void doWithMarshaller(T object, ThrowingBiConsumer<? super T, ? supe
194194
}
195195

196196
public <T> T unmarshal(InputStream inputStream, Class<T> type) {
197-
Source xmlSource = new SAXSource(saxParserProvider.createXMLReader(), new InputSource(inputStream));
197+
Source xmlSource = saxParserProvider.createSource(new InputSource(inputStream));
198198
return unmarshal(unmarshaller -> unmarshaller.unmarshal(xmlSource), type);
199199
}
200200

jaxb/src/main/java/no/digipost/xml/bind/MarshallerCustomizer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package no.digipost.xml.bind;
1717

18+
import no.digipost.xml.validation.SchemaHelper;
19+
1820
import javax.xml.bind.Marshaller;
1921
import javax.xml.validation.Schema;
2022

@@ -29,7 +31,7 @@ public interface MarshallerCustomizer {
2931
public static MarshallerCustomizer validateUsingSchemaResources(Set<String> schemaResources) {
3032
return Optional.ofNullable(schemaResources)
3133
.filter(s -> !s.isEmpty())
32-
.map(XmlUtils::createSchema)
34+
.map(SchemaHelper::createW3cXmlSchema)
3335
.map(MarshallerCustomizer::validateUsingSchema)
3436
.orElse(NO_CUSTOMIZATION);
3537
}

jaxb/src/main/java/no/digipost/xml/bind/UnmarshallerCustomizer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package no.digipost.xml.bind;
1717

18+
import no.digipost.xml.validation.SchemaHelper;
19+
1820
import javax.xml.bind.Unmarshaller;
1921
import javax.xml.validation.Schema;
2022

@@ -29,7 +31,7 @@ public interface UnmarshallerCustomizer {
2931
public static UnmarshallerCustomizer validateUsingSchemaResources(Set<String> schemaResources) {
3032
return Optional.ofNullable(schemaResources)
3133
.filter(s -> !s.isEmpty())
32-
.map(XmlUtils::createSchema)
34+
.map(SchemaHelper::createW3cXmlSchema)
3335
.map(UnmarshallerCustomizer::validateUsingSchema)
3436
.orElse(NO_CUSTOMIZATION);
3537
}

jaxb/src/main/java/no/digipost/xml/bind/XmlUtils.java

Lines changed: 0 additions & 91 deletions
This file was deleted.

jaxb/src/main/java/no/digipost/signature/jaxb/SaxParserProvider.java renamed to jaxb/src/main/java/no/digipost/xml/parsers/SaxParserProvider.java

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package no.digipost.signature.jaxb;
16+
package no.digipost.xml.parsers;
1717

18+
import org.xml.sax.InputSource;
1819
import org.xml.sax.SAXException;
1920
import org.xml.sax.SAXNotRecognizedException;
2021
import org.xml.sax.SAXNotSupportedException;
@@ -23,12 +24,13 @@
2324
import javax.xml.parsers.ParserConfigurationException;
2425
import javax.xml.parsers.SAXParser;
2526
import javax.xml.parsers.SAXParserFactory;
27+
import javax.xml.transform.sax.SAXSource;
2628

2729
import static javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING;
2830

29-
class SaxParserProvider {
31+
public interface SaxParserProvider {
3032

31-
public static SaxParserProvider createSecuredParserFactory() {
33+
public static SaxParserProvider createSecuredProvider() {
3234
SAXParserFactory factory = SAXParserFactory.newInstance();
3335
factory.setNamespaceAware(true);
3436

@@ -37,40 +39,23 @@ public static SaxParserProvider createSecuredParserFactory() {
3739
try {
3840
factory.setFeature(FEATURE_SECURE_PROCESSING, true);
3941
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
40-
factory.setValidating(false); // this only concerns DTD validation
42+
factory.setValidating(false); // this only concerns DTD validation
4143
factory.setXIncludeAware(false); // already false by default, but setting anyway
4244
} catch (SAXNotRecognizedException | SAXNotSupportedException | ParserConfigurationException e) {
4345
throw new IllegalStateException(
4446
"Unable to configure " + factory.getClass().getName() + " for secure processing " +
4547
"because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
4648
}
4749

48-
return new SaxParserProvider(factory);
50+
return new SharedFactorySaxParserProvider(factory);
4951
}
5052

5153

52-
/**
53-
* A configured SAXParserFactory's {@link SAXParserFactory#newSAXParser() newSAXParser()} method is
54-
* <a href="https://jcp.org/aboutJava/communityprocess/review/jsr063/jaxp-pd2.pdf">expected by specification to be thread safe (ch. 3.4)</a>
55-
*/
56-
private final SAXParserFactory saxParserFactory;
5754

58-
public SaxParserProvider(SAXParserFactory saxParserFactory) {
59-
this.saxParserFactory = saxParserFactory;
60-
}
55+
SAXParser createParser();
6156

6257

63-
public SAXParser createParser() {
64-
try {
65-
return saxParserFactory.newSAXParser();
66-
} catch (ParserConfigurationException | SAXException e) {
67-
throw new IllegalStateException(
68-
"Unable to create new SAX parser from " + saxParserFactory.getClass().getName() +
69-
" because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
70-
}
71-
}
72-
73-
public XMLReader createXMLReader() {
58+
default XMLReader createXMLReader() {
7459
SAXParser parser = createParser();
7560
try {
7661
return parser.getXMLReader();
@@ -80,4 +65,8 @@ public XMLReader createXMLReader() {
8065
" because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
8166
}
8267
}
68+
69+
default SAXSource createSource(InputSource inputSource) {
70+
return new SAXSource(createXMLReader(), inputSource);
71+
}
8372
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (C) Posten Norge AS
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package no.digipost.xml.parsers;
17+
18+
import org.xml.sax.SAXException;
19+
20+
import javax.xml.parsers.ParserConfigurationException;
21+
import javax.xml.parsers.SAXParser;
22+
import javax.xml.parsers.SAXParserFactory;
23+
24+
class SharedFactorySaxParserProvider implements SaxParserProvider {
25+
26+
/**
27+
* A configured SAXParserFactory's {@link SAXParserFactory#newSAXParser() newSAXParser()} method is
28+
* <a href="https://jcp.org/aboutJava/communityprocess/review/jsr063/jaxp-pd2.pdf">expected by specification to be thread safe (ch. 3.4)</a>
29+
*/
30+
private final SAXParserFactory saxParserFactory;
31+
32+
SharedFactorySaxParserProvider(SAXParserFactory saxParserFactory) {
33+
this.saxParserFactory = saxParserFactory;
34+
}
35+
36+
37+
@Override
38+
public SAXParser createParser() {
39+
try {
40+
return saxParserFactory.newSAXParser();
41+
} catch (ParserConfigurationException | SAXException e) {
42+
throw new IllegalStateException(
43+
"Unable to create new SAX parser from " + saxParserFactory.getClass().getName() +
44+
" because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
45+
}
46+
}
47+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright (C) Posten Norge AS
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package no.digipost.xml.transform.sax;
17+
18+
import org.xml.sax.InputSource;
19+
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
import java.io.Reader;
23+
import java.io.UncheckedIOException;
24+
import java.net.URL;
25+
import java.nio.charset.Charset;
26+
27+
import static java.util.Objects.requireNonNull;
28+
29+
public class UrlInputSource extends InputSource {
30+
31+
public static UrlInputSource fromClasspath(String resourceName, Charset encoding) {
32+
return fromClasspath(resourceName, encoding, UrlInputSource.class.getClassLoader());
33+
}
34+
35+
public static UrlInputSource fromClasspath(String resourceName, Charset encoding, ClassLoader classLoader) {
36+
URL location = classLoader.getResource(resourceName.startsWith("/") ? resourceName.substring(1) : resourceName);
37+
requireNonNull(location, resourceName + " not found on classpath");
38+
return new UrlInputSource(location, encoding);
39+
}
40+
41+
private URL location;
42+
43+
public UrlInputSource(URL location, Charset encoding) {
44+
this.location = location;
45+
this.setEncoding(encoding.name());
46+
this.setSystemId(location.toString());
47+
}
48+
49+
@Override
50+
public final InputStream getByteStream() {
51+
try {
52+
return location.openStream();
53+
} catch (IOException e) {
54+
throw new UncheckedIOException(
55+
"Unable to obtain InputStream for " + location +
56+
" because " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
57+
}
58+
}
59+
60+
@Override
61+
public final String getEncoding() {
62+
return super.getEncoding();
63+
}
64+
65+
@Override
66+
public final Reader getCharacterStream() {
67+
return null;
68+
}
69+
70+
}

0 commit comments

Comments
 (0)