Skip to content

Commit

Permalink
Merge pull request #300 from digipost/disable-xml-entity-expansions
Browse files Browse the repository at this point in the history
Disable XML Entity Expansions for unmarshalling
  • Loading branch information
runeflobakk authored Nov 2, 2023
2 parents 37c696b + a077a9b commit 0b146da
Show file tree
Hide file tree
Showing 20 changed files with 952 additions and 85 deletions.
4 changes: 2 additions & 2 deletions jaxb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@
<dependency>
<groupId>nl.jqno.equalsverifier</groupId>
<artifactId>equalsverifier</artifactId>
<version>3.15.1</version>
<version>3.15.3</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -193,7 +193,7 @@
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
<version>3.6.0</version>
<configuration>
<detectOfflineLinks>false</detectOfflineLinks>
<doclint>all,-missing,-html</doclint>
Expand Down
114 changes: 38 additions & 76 deletions jaxb/src/main/java/no/digipost/signature/jaxb/JaxbMarshaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,26 @@
package no.digipost.signature.jaxb;

import no.digipost.signature.xsd.SignatureApiSchemas;
import no.digipost.xml.bind.MarshallingCustomization;
import no.digipost.xml.parsers.SaxParserProvider;
import no.digipost.xml.transform.sax.SaxInputSources;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.w3c.dom.Node;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.net.URL;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static no.digipost.xml.bind.MarshallingCustomization.validateUsingSchemaResources;

/**
* @see JaxbMarshaller.ForResponsesOfAllApis
Expand All @@ -71,7 +59,7 @@ private static final class SingletonHolder {
}

public ForRequestsOfAllApis() {
super(SignatureMarshalling.allApiRequestClasses(), SignatureApiSchemas.DIRECT_AND_PORTAL_API);
super(validateUsingSchemaResources(SignatureApiSchemas.DIRECT_AND_PORTAL_API), SignatureMarshalling.allApiRequestClasses());
}
}

Expand All @@ -91,7 +79,7 @@ private static final class SingletonHolder {
}

public ForResponsesOfAllApis() {
super(SignatureMarshalling.allApiResponseClasses());
super(MarshallingCustomization.NO_CUSTOMIZATION, SignatureMarshalling.allApiResponseClasses());
}
}

Expand Down Expand Up @@ -119,72 +107,42 @@ private static final class SingletonHolder {
}

public ForAllApis() {
super(SignatureMarshalling.allApiClasses(), SignatureApiSchemas.DIRECT_AND_PORTAL_API);
super(validateUsingSchemaResources(SignatureApiSchemas.DIRECT_AND_PORTAL_API), SignatureMarshalling.allApiClasses());
}
}

private static InputSource createInputSource(String resource) {
URL resourceUrl = requireNonNull(JaxbMarshaller.class.getResource(resource), resource);
try (InputStream inputStream = resourceUrl.openStream()) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

final int bufLen = 1024;
byte[] buf = new byte[bufLen];
int readLen;
while ((readLen = inputStream.read(buf, 0, bufLen)) != -1)
outputStream.write(buf, 0, readLen);

InputSource source = new InputSource(new ByteArrayInputStream(outputStream.toByteArray()));
source.setSystemId(resourceUrl.toString());
return source;
} catch (IOException e) {
throw new UncheckedIOException(
"Unable to resolve " + resource + " from " + resourceUrl + ", " +
"because " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e);
}
}
private final JAXBContext jaxbContext;
private final MarshallingCustomization marshallingCustomization;
private final SaxParserProvider saxParserProvider;

private static Schema createSchema(Collection<String> resources) {
try {
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
parserFactory.setFeature("http://xml.org/sax/features/namespace-prefixes", true);

SAXParser saxParser = parserFactory.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
Source[] schemaSources = resources.stream()
.map(resource -> new SAXSource(xmlReader, createInputSource(resource)))
.toArray(Source[]::new);

SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = schemaFactory.newSchema(schemaSources);
return schema;
} catch (Exception e) {
throw new RuntimeException("Could not create schema from resources [" + String.join(", ", resources) + "]", e);
}
public JaxbMarshaller(MarshallingCustomization marshallingCustomization, Class<?> ... classesToBeBound) {
this.jaxbContext = JaxbUtils.initContext(classesToBeBound);
this.marshallingCustomization = marshallingCustomization;
this.saxParserProvider = SaxParserProvider.createSecuredProvider();
}

private static JAXBContext initContext(Collection<Class<?>> classes) {
Class<?>[] classesToBeBound = classes.toArray(new Class[classes.size()]);
try {
return JAXBContext.newInstance(classesToBeBound);
} catch (JAXBException e) {
throw new RuntimeException("Could not create JAXBContext for classes [" + Stream.of(classesToBeBound).map(Class::getSimpleName).collect(joining(",")) + "]" , e);
}
public JaxbMarshaller(MarshallingCustomization marshallingCustomization, Set<Class<?>> classesToBeBound) {
this(marshallingCustomization, classesToBeBound.toArray(new Class[classesToBeBound.size()]));
}

private final JAXBContext jaxbContext;
private final Optional<Schema> schema;
public JaxbMarshaller(Class<?> ... classesToBeBound) {
this(MarshallingCustomization.NO_CUSTOMIZATION, classesToBeBound);
}

public JaxbMarshaller(Set<Class<?>> classes, Set<String> schemaResources) {
this.jaxbContext = initContext(classes);
this.schema = Optional.ofNullable(schemaResources).filter(s -> !s.isEmpty()).map(JaxbMarshaller::createSchema);
public JaxbMarshaller(Set<Class<?>> classesToBeBound) {
this(MarshallingCustomization.NO_CUSTOMIZATION, classesToBeBound);
}

public JaxbMarshaller(Set<Class<?>> classes) {
this(classes, null);
/**
* @deprecated Use {@link #JaxbMarshaller(MarshallingCustomization, Set)} with
* {@link MarshallingCustomization#validateUsingSchemaResources(Set)}
*/
@Deprecated
public JaxbMarshaller(Set<Class<?>> classesToBeBound, Set<String> schemaResources) {
this(MarshallingCustomization.validateUsingSchemaResources(schemaResources), classesToBeBound);
}


public String marshalToString(Object object) {
return marshalToResult(object, xml -> xml.toString(UTF_8.name()));
}
Expand Down Expand Up @@ -228,26 +186,30 @@ private interface ThrowingBiConsumer<T, S> {
private <T> void doWithMarshaller(T object, ThrowingBiConsumer<? super T, ? super Marshaller> operation) {
try {
Marshaller marshaller = jaxbContext.createMarshaller();
schema.ifPresent(marshaller::setSchema);
marshallingCustomization.customize(marshaller);
operation.accept(object, marshaller);
} catch (Exception e) {
throw SignatureMarshalException.failedMarshal(object, e);
}
}


public <T> T unmarshal(InputStream inputStream, Class<T> type) {
return unmarshal(unmarshaller -> unmarshaller.unmarshal(inputStream), type);
Source xmlSource = saxParserProvider.createSource(SaxInputSources.fromInputStreamPreventClose(inputStream));
return unmarshal(unmarshaller -> unmarshaller.unmarshal(xmlSource), type);
}

public <T> T unmarshal(byte[] bytes, Class<T> type) {
return unmarshal(new ByteArrayInputStream(bytes), type);
}

public <T> T unmarshal(Node node, Class<T> type) {
return unmarshal(unmarshaller -> unmarshaller.unmarshal(node), type);
}

private <T> T unmarshal(ThrowingFunction<? super Unmarshaller, ?> operation, Class<T> type) {
try {
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
schema.ifPresent(unmarshaller::setSchema);
marshallingCustomization.customize(unmarshaller);
return type.cast(operation.apply(unmarshaller));
} catch (Exception e) {
throw SignatureMarshalException.failedUnmarshal(type, e);
Expand Down
39 changes: 39 additions & 0 deletions jaxb/src/main/java/no/digipost/signature/jaxb/JaxbUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) Posten Norge AS
*
* Licensed 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 no.digipost.signature.jaxb;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

final class JaxbUtils {

public static JAXBContext initContext(Class<?> ... classesToBeBound) {
try {
return JAXBContext.newInstance(classesToBeBound);
} catch (JAXBException e) {
throw new RuntimeException(
"Could not create JAXBContext for classes [" + Stream.of(classesToBeBound).map(Class::getSimpleName).collect(joining(",")) + "] " +
"because " + e.getClass().getSimpleName() + " '" + e.getMessage() + "'", e);
}
}

private JaxbUtils() {
}
}
59 changes: 59 additions & 0 deletions jaxb/src/main/java/no/digipost/xml/bind/MarshallerCustomizer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (C) Posten Norge AS
*
* Licensed 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 no.digipost.xml.bind;

import no.digipost.xml.validation.SchemaHelper;

import javax.xml.bind.Marshaller;
import javax.xml.validation.Schema;

import java.util.Optional;
import java.util.Set;

@FunctionalInterface
public interface MarshallerCustomizer {

final MarshallerCustomizer NO_CUSTOMIZATION = new NoCustomization("MarshallerCustomizer.NO_CUSTOMIZATION");

public static MarshallerCustomizer validateUsingSchemaResources(Set<String> schemaResources) {
return Optional.ofNullable(schemaResources)
.filter(s -> !s.isEmpty())
.map(SchemaHelper::createW3cXmlSchema)
.map(MarshallerCustomizer::validateUsingSchema)
.orElse(NO_CUSTOMIZATION);
}

public static MarshallerCustomizer validateUsingSchema(Schema schema) {
return marshaller -> marshaller.setSchema(schema);
}



void customize(Marshaller marshaller) throws Exception;


default MarshallerCustomizer andThen(MarshallerCustomizer nextCustomization) {
if (nextCustomization instanceof NoCustomization) {
return this;
} else {
return marshaller -> {
this.customize(marshaller);
nextCustomization.customize(marshaller);
};
}
}

}
Loading

0 comments on commit 0b146da

Please sign in to comment.