Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,9 @@ private final String _collectUntilTag() throws XMLStreamException
// note: SPACE is ignorable (and seldom seen), not to be included
case XMLStreamConstants.CHARACTERS:
case XMLStreamConstants.CDATA:
// Only reported if the reader does not replace entity references
// (or could not expand this one): still part of text content
case XMLStreamConstants.ENTITY_REFERENCE:
// 17-Jul-2017, tatu: as per [dataformat-xml#236], need to try to...
{
String str = _getText(_xmlReader);
Expand Down Expand Up @@ -684,6 +687,7 @@ private final int _skipAndCollectTextUntilTag() throws XMLStreamException
// note: SPACE is ignorable (and seldom seen), not to be included
case XMLStreamConstants.CHARACTERS:
case XMLStreamConstants.CDATA:
case XMLStreamConstants.ENTITY_REFERENCE:
{
String str = _getText(_xmlReader);
if (chars == null) {
Expand All @@ -705,15 +709,24 @@ private final int _skipAndCollectTextUntilTag() throws XMLStreamException

private final String _getText(XMLStreamReader2 r) throws XMLStreamException
{
final String text;
try {
return r.getText();
text = r.getText();
} catch (RuntimeException e) {
Throwable cause = e.getCause();
if (cause instanceof XMLStreamException xse) {
throw xse;
}
throw e;
}
// An entity reference the reader did not (or could not) expand has no
// replacement text to offer: fail rather than silently drop it from content
if (text == null && r.getEventType() == XMLStreamConstants.ENTITY_REFERENCE) {
throw new XMLStreamException("Unexpanded entity reference '&"+r.getLocalName()
+";' in text content (entity not declared, or not replaced by XMLStreamReader)",
r.getLocation());
}
return text;
}

/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package tools.jackson.dataformat.xml.stream;

import java.util.Map;

import javax.xml.stream.XMLInputFactory;

import org.junit.jupiter.api.Test;

import com.ctc.wstx.stax.WstxInputFactory;

import tools.jackson.core.exc.StreamReadException;

import tools.jackson.dataformat.xml.XmlFactory;
import tools.jackson.dataformat.xml.XmlMapper;
import tools.jackson.dataformat.xml.XmlTestUtil;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

// Readers configured with `IS_REPLACING_ENTITY_REFERENCES` disabled report
// general entity references in text content as `ENTITY_REFERENCE` events.
// `XmlTokenStream` used to ignore those while collecting text, silently
// dropping part of the content: either the replacement text has to be used,
// or (if the reader has none to offer) reading must fail.
public class EntityReferenceReadTest extends XmlTestUtil
{
private final static String DOC_INTERNAL_ENTITY =
"<!DOCTYPE root [<!ENTITY e 'xx'>]>\n"
+"<root><a>foo&e;bar</a></root>";

private final static String DOC_UNDECLARED_ENTITY =
"<root><a>foo&e;bar</a></root>";

// Text collected after an END_ELEMENT goes through a different code path
// (mixed content), so cover that one too
private final static String DOC_MIXED_CONTENT =
"<!DOCTYPE root [<!ENTITY e 'xx'>]>\n"
+"<root><a>1</a>foo&e;bar<b>2</b></root>";

private final XmlMapper NON_REPLACING_MAPPER = _nonReplacingMapper(true);

private final XmlMapper NON_REPLACING_NO_DTD_MAPPER = _nonReplacingMapper(false);

private static XmlMapper _nonReplacingMapper(boolean supportDTD) {
XMLInputFactory f = new WstxInputFactory();
f.setProperty(XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE);
f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
f.setProperty(XMLInputFactory.SUPPORT_DTD, supportDTD);
return mapperBuilder(XmlFactory.builder().xmlInputFactory(f).build()).build();
}

@Test
public void testDeclaredEntityNotReplacedByReader() throws Exception
{
Map<?,?> result = NON_REPLACING_MAPPER.readValue(DOC_INTERNAL_ENTITY, Map.class);
assertEquals("fooxxbar", result.get("a"));
}

@Test
public void testDeclaredEntityNotReplacedByReaderMixedContent() throws Exception
{
Map<?,?> result = NON_REPLACING_MAPPER.readValue(DOC_MIXED_CONTENT, Map.class);
assertEquals("1", result.get("a"));
assertEquals("2", result.get("b"));
assertEquals("fooxxbar", result.get(""));
}

@Test
public void testUndeclaredEntityNotReplacedByReader() throws Exception
{
// No DTD, so no replacement text for the reader to hand out: must fail,
// not quietly produce "foobar"
StreamReadException e = assertThrows(StreamReadException.class,
() -> NON_REPLACING_NO_DTD_MAPPER.readValue(DOC_UNDECLARED_ENTITY, Map.class));
verifyException(e, "Unexpanded entity reference '&e;'");
}

@Test
public void testDefaultReaderStillReplaces() throws Exception
{
// Default (replacing) reader with DTD support enabled expands the entity
// itself; no change in behavior there
XMLInputFactory f = new WstxInputFactory();
f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
f.setProperty(XMLInputFactory.SUPPORT_DTD, Boolean.TRUE);
XmlMapper mapper = mapperBuilder(XmlFactory.builder().xmlInputFactory(f).build()).build();
Map<?,?> result = mapper.readValue(DOC_INTERNAL_ENTITY, Map.class);
assertEquals("fooxxbar", result.get("a"));
}
}
Loading