Fix marshalling invalid XML characters

If someone tries to persist data with invalid XML characters, they now get filtered out.

Committed-by: Rene Pfeuffer <rene.pfeuffer@cloudogu.com>
This commit is contained in:
Thomas Zerr
2023-08-10 14:20:07 +02:00
parent f54cad3b05
commit 1750eae2d1
7 changed files with 684 additions and 3 deletions

View File

@@ -0,0 +1,2 @@
- type: fixed
description: Marshalling of invalid xml characters

View File

@@ -0,0 +1,226 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.xml;
import javax.xml.namespace.NamespaceContext;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
public class FilterInvalidCharXMLStreamWriter implements XMLStreamWriter, AutoCloseable {
private final XMLStreamWriter writer;
private final static String invalidXmlCharsPattern = "[" + "\u0000-\u0008" + "\u000B-\u000C" + "\u000E-\u001F" + "\uD800-\uDFFF" + "\uFFFE-\uFFFF" + "]";
public FilterInvalidCharXMLStreamWriter(XMLStreamWriter writer) {
this.writer = writer;
}
@Override
public void writeStartElement(String localName) throws XMLStreamException {
writer.writeStartElement(filterInvalidXmlChars(localName));
}
@Override
public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
writer.writeStartElement(filterInvalidXmlChars(namespaceURI), filterInvalidXmlChars(localName));
}
@Override
public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
writer.writeStartElement(
filterInvalidXmlChars(prefix),
filterInvalidXmlChars(localName),
filterInvalidXmlChars(namespaceURI)
);
}
@Override
public void writeEmptyElement(String localName) throws XMLStreamException {
writer.writeEmptyElement(filterInvalidXmlChars(localName));
}
@Override
public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
writer.writeEmptyElement(filterInvalidXmlChars(namespaceURI), filterInvalidXmlChars(localName));
}
@Override
public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
writer.writeEmptyElement(
filterInvalidXmlChars(prefix),
filterInvalidXmlChars(localName),
filterInvalidXmlChars(namespaceURI)
);
}
@Override
public void writeEndElement() throws XMLStreamException {
writer.writeEndElement();
}
@Override
public void writeEndDocument() throws XMLStreamException {
writer.writeEndDocument();
}
@Override
public void writeAttribute(String localName, String value) throws XMLStreamException {
writer.writeAttribute(filterInvalidXmlChars(localName), filterInvalidXmlChars(value));
}
@Override
public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
writer.writeAttribute(
filterInvalidXmlChars(namespaceURI),
filterInvalidXmlChars(localName),
filterInvalidXmlChars(value)
);
}
@Override
public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
writer.writeAttribute(
filterInvalidXmlChars(prefix),
filterInvalidXmlChars(namespaceURI),
filterInvalidXmlChars(localName),
filterInvalidXmlChars(value)
);
}
@Override
public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
writer.writeNamespace(filterInvalidXmlChars(prefix), filterInvalidXmlChars(namespaceURI));
}
@Override
public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
writer.writeDefaultNamespace(filterInvalidXmlChars(namespaceURI));
}
@Override
public void writeComment(String data) throws XMLStreamException {
writer.writeComment(filterInvalidXmlChars(data));
}
@Override
public void writeProcessingInstruction(String target) throws XMLStreamException {
writer.writeProcessingInstruction(filterInvalidXmlChars(target));
}
@Override
public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
writer.writeProcessingInstruction(filterInvalidXmlChars(target), filterInvalidXmlChars(data));
}
@Override
public void writeCData(String data) throws XMLStreamException {
writer.writeCData(filterInvalidXmlChars(data));
}
@Override
public void writeDTD(String dtd) throws XMLStreamException {
writer.writeDTD(filterInvalidXmlChars(dtd));
}
@Override
public void writeEntityRef(String name) throws XMLStreamException {
writer.writeEntityRef(filterInvalidXmlChars(name));
}
@Override
public void writeStartDocument() throws XMLStreamException {
writer.writeStartDocument();
}
@Override
public void writeStartDocument(String version) throws XMLStreamException {
writer.writeStartDocument(filterInvalidXmlChars(version));
}
@Override
public void writeStartDocument(String encoding, String version) throws XMLStreamException {
writer.writeStartDocument(filterInvalidXmlChars(encoding), filterInvalidXmlChars(version));
}
@Override
public void writeCharacters(String text) throws XMLStreamException {
writer.writeCharacters(filterInvalidXmlChars(text));
}
@Override
public void writeCharacters(char[] text, int start, int len) throws XMLStreamException {
StringBuilder sb = new StringBuilder();
for(int i = start; i < start + len; ++i) {
sb.append(text[i]);
}
writer.writeCharacters(filterInvalidXmlChars(sb.toString()));
}
@Override
public String getPrefix(String uri) throws XMLStreamException {
return writer.getPrefix(filterInvalidXmlChars(uri));
}
@Override
public void setPrefix(String prefix, String uri) throws XMLStreamException {
writer.setPrefix(filterInvalidXmlChars(prefix), filterInvalidXmlChars(uri));
}
@Override
public void setDefaultNamespace(String uri) throws XMLStreamException {
writer.setDefaultNamespace(filterInvalidXmlChars(uri));
}
@Override
public void setNamespaceContext(NamespaceContext context) throws XMLStreamException {
writer.setNamespaceContext(context);
}
@Override
public NamespaceContext getNamespaceContext() {
return writer.getNamespaceContext();
}
@Override
public Object getProperty(String name) throws IllegalArgumentException {
return writer.getProperty(filterInvalidXmlChars(name));
}
@Override
public void close() throws XMLStreamException {
writer.close();
}
@Override
public void flush() throws XMLStreamException {
writer.flush();
}
private String filterInvalidXmlChars(String input) {
return input.replaceAll(invalidXmlCharsPattern, "");
}
}

View File

@@ -0,0 +1,449 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.xml;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class FilterInvalidCharXMLStreamWriterTest {
private static List<Integer> invalidXmlChars;
@BeforeAll
static void setupInvalidChars() {
invalidXmlChars = new ArrayList<>();
for(int i = 0; i < 0x110000; ++i) {
if(i == 0x9 || i == 0xA || i == 0xD || (i >= 0x20 && i <= 0xD7FF) || (i >= 0xE000 && i <= 0xFFFD) || (i >= 0x10000 && i <= 0x10FFFF)) {
continue;
}
invalidXmlChars.add(i);
}
}
@Test
void shouldWriteDefaultStartOfXml() throws XMLStreamException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartDocument();
}
assertThat(outputStream.toString()).isEqualTo("<?xml version=\"1.0\" ?>");
}
@Test
void shouldWriteStartOfXmlWithVersion() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartDocument(addInvalidChars("1.1", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<?xml version=\"1.1\"?>");
}
}
@Test
void shouldWriteStartOfXmlWithVersionAndEncoding() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartDocument(addInvalidChars("UTF-8", invalidChar), addInvalidChars("1.1", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<?xml version=\"1.1\" encoding=\"UTF-8\"?>");
}
}
@Test
void shouldWriteStartElement() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement(addInvalidChars("Root", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<Root");
}
}
@Test
void shouldWriteStartElementWithNamespace() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.setPrefix(
addInvalidChars("pre", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
writer.writeStartElement(
addInvalidChars("https://www.test-namespace.org/", invalidChar),
addInvalidChars("Root", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<pre:Root");
}
}
@Test
void shouldWriteStartElementWithNamespaceAndPrefix() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement(
addInvalidChars("other_pre", invalidChar),
addInvalidChars("Root", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<other_pre:Root");
}
}
@Test
void shouldWriteEmptyElement() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeEmptyElement(addInvalidChars("Root", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<Root");
}
}
@Test
void shouldWriteEmptyElementWithNamespace() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.setPrefix(
addInvalidChars("pre", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
writer.writeEmptyElement(
addInvalidChars("https://www.test-namespace.org/", invalidChar),
addInvalidChars("Root", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<pre:Root");
}
}
@Test
void shouldWriteEmptyElementWithNamespaceAndPrefix() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeEmptyElement(
addInvalidChars("other_pre", invalidChar),
addInvalidChars("Root", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<other_pre:Root");
}
}
@Test
void shouldWriteEndOfElement() throws XMLStreamException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeEndElement();
}
assertThat(outputStream.toString()).isEqualTo("<Root></Root>");
}
@Test
void shouldWriteEndOfDocument() throws XMLStreamException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeStartElement("Child");
writer.writeEndDocument();
}
assertThat(outputStream.toString()).isEqualTo("<Root><Child></Child></Root>");
}
@Test
void shouldWriteAttribute() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeAttribute(addInvalidChars("attribute", invalidChar), addInvalidChars("value", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<Root attribute=\"value\"");
}
}
@Test
void shouldWriteAttributeWithNamespace() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.setPrefix(
addInvalidChars("pre", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
writer.writeStartElement("Root");
writer.writeAttribute(
addInvalidChars("https://www.test-namespace.org/", invalidChar),
addInvalidChars("attribute", invalidChar),
addInvalidChars("value", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<Root pre:attribute=\"value\"");
}
}
@Test
void shouldWriteAttributeWithNamespaceAndPrefix() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeAttribute(
addInvalidChars("other_pre", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar),
addInvalidChars("attribute", invalidChar),
addInvalidChars("value", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<Root other_pre:attribute=\"value\"");
}
}
@Test
void shouldWriteNamespace() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeNamespace(
addInvalidChars("pre", invalidChar),
addInvalidChars("https://www.test-namespace.org/", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<Root xmlns:pre=\"https://www.test-namespace.org/\"");
}
}
@Test
void shouldWriteDefaultNamespace() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeStartElement("Root");
writer.writeDefaultNamespace(addInvalidChars("https://www.test-namespace.org/", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<Root xmlns=\"https://www.test-namespace.org/\"");
}
}
@Test
void shouldWriteComment() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeComment(addInvalidChars("Comment", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<!--Comment-->");
}
}
@Test
void shouldWriteProcessingInstruction() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeProcessingInstruction(addInvalidChars("Target", invalidChar));
}
assertThat(outputStream.toString()).isEqualTo("<?Target?>");
}
}
@Test
void shouldWriteProcessingInstructionWithData() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeProcessingInstruction(
addInvalidChars("Target", invalidChar),
addInvalidChars("InstructionData", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<?Target InstructionData?>");
}
}
@Test
void shouldWriteCData() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeCData(
addInvalidChars("Data", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<![CDATA[Data]]>");
}
}
@Test
void shouldWriteDtd() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeDTD(
addInvalidChars("<!DOCTYPE>", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("<!DOCTYPE>");
}
}
@Test
void shouldWriteEntityRef() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeEntityRef(
addInvalidChars("Name", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("&Name;");
}
}
@Test
void shouldWriteCharactersFromString() throws XMLStreamException {
for (int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeCharacters(
addInvalidChars("&<Some Random String>&", invalidChar)
);
}
assertThat(outputStream.toString()).isEqualTo("&amp;&lt;Some Random String&gt;&amp;");
}
}
@ParameterizedTest
@CsvSource({"test,0,4,test", "test,1,2,es"})
void shouldWriteCharactersFromBuffer(String text, String start, String len, String expectedResult) throws XMLStreamException {
char[] chars = text.toCharArray();
int startIndex = Integer.parseInt(start);
int length = Integer.parseInt(len);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
writer.writeCharacters(chars, startIndex, length);
}
assertThat(outputStream.toString()).isEqualTo(expectedResult);
}
@Test
void shouldWriteCharactersFromBuffer() throws XMLStreamException {
for(int invalidChar : invalidXmlChars) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try (FilterInvalidCharXMLStreamWriter writer = new FilterInvalidCharXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(outputStream))) {
char[] unfilteredChars = addInvalidChars("Test", invalidChar).toCharArray();
writer.writeCharacters(unfilteredChars, 0, unfilteredChars.length);
}
assertThat(outputStream.toString()).isEqualTo("Test");
}
}
private String addInvalidChars(String input, int invalidChar) {
StringBuilder sb = new StringBuilder();
sb.appendCodePoint(invalidChar);
sb.append(input);
sb.appendCodePoint(invalidChar);
return sb.toString();
}
}

View File

@@ -29,6 +29,7 @@ import com.google.common.collect.ImmutableMap.Builder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.security.KeyGenerator;
import sonia.scm.xml.XmlStreams;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
@@ -75,7 +76,7 @@ public class JAXBDataStore<T> extends FileBasedStore<T> implements DataStore<T>
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
CopyOnWrite.withTemporaryFile(
temp -> marshaller.marshal(item, temp.toFile()),
temp -> marshaller.marshal(item, XmlStreams.createWriter(temp.toFile())),
file.toPath(),
() -> cache.put(file, item)
);

View File

@@ -24,6 +24,8 @@
package sonia.scm.store;
import sonia.scm.xml.XmlStreams;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
@@ -73,7 +75,7 @@ final class TypedStoreContext<T> {
}
void marshal(Object object, File file) {
withMarshaller(marshaller -> marshaller.marshal(object, file));
withMarshaller(marshaller -> marshaller.marshal(object, XmlStreams.createWriter(file)));
}
void withMarshaller(ThrowingConsumer<Marshaller> consumer) {

View File

@@ -95,7 +95,8 @@ public final class XmlStreams {
private static AutoCloseableXMLWriter createWriter(Writer writer) throws XMLStreamException {
IndentXMLStreamWriter indentXMLStreamWriter = new IndentXMLStreamWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(writer));
return new AutoCloseableXMLWriter(indentXMLStreamWriter, writer);
FilterInvalidCharXMLStreamWriter filterInvalidCharXMLStreamWriter = new FilterInvalidCharXMLStreamWriter(indentXMLStreamWriter);
return new AutoCloseableXMLWriter(filterInvalidCharXMLStreamWriter, writer);
}
public static final class AutoCloseableXMLReader extends StreamReaderDelegate implements AutoCloseable {