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
2 changes: 2 additions & 0 deletions release-notes/CREDITS
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,5 @@ Christian Beikov (@beikov)
* Fixed #899: Return `null` from `nextStringValue()` at end-of-input (instead
of throwing `IllegalStateException`)
(3.3.0)
* Fixed #903: Split embedded `]]>` across CDATA sections when serializing
(3.3.0)
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Version: 3.x (for earlier see VERSION-2.x)
#899: Return `null` from `nextStringValue()` at end-of-input (instead of
throwing `IllegalStateException`)
(fix by @Sahana2524)
#903: Split embedded `]]>` across CDATA sections when serializing
(fix by @Sahana2524)

3.2.2 (14-Aug-2026)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -856,7 +856,7 @@ public JsonGenerator writeString(String text) throws JacksonException
// but for now, let's just make sure structure is correct
//if (_xmlPrettyPrinter != null) { ... }
if(_nextIsCData) {
_xmlWriter.writeCData(text);
StaxUtil.writeCData(_xmlWriter, text);
} else {
_xmlWriter.writeCharacters(text);
}
Expand All @@ -867,7 +867,7 @@ public JsonGenerator writeString(String text) throws JacksonException
} else {
_xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
if(_nextIsCData) {
_xmlWriter.writeCData(text);
StaxUtil.writeCData(_xmlWriter, text);
} else {
_xmlWriter.writeCharacters(text);
}
Expand All @@ -892,7 +892,7 @@ public JsonGenerator writeString(char[] text, int offset, int len) throws Jackso
} else if (checkNextIsUnwrapped()) {
// should we consider pretty-printing or not?
if(_nextIsCData) {
_xmlWriter.writeCData(text, offset, len);
StaxUtil.writeCData(_xmlWriter, text, offset, len);
} else {
_xmlWriter.writeCharacters(text, offset, len);
}
Expand All @@ -903,7 +903,7 @@ public JsonGenerator writeString(char[] text, int offset, int len) throws Jackso
} else {
_xmlWriter.writeStartElement(_nextName.getNamespaceURI(), _nextName.getLocalPart());
if(_nextIsCData) {
_xmlWriter.writeCData(text, offset, len);
StaxUtil.writeCData(_xmlWriter, text, offset, len);
} else {
_xmlWriter.writeCharacters(text, offset, len);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ public void writeLeafElement(XMLStreamWriter2 sw,
}
sw.writeStartElement(nsURI, localName);
if(isCData) {
sw.writeCData(text);
StaxUtil.writeCData(sw, text);
} else {
sw.writeCharacters(text);
}
Expand All @@ -309,7 +309,7 @@ public void writeLeafElement(XMLStreamWriter2 sw,
}
sw.writeStartElement(nsURI, localName);
if(isCData) {
sw.writeCData(buffer, offset, len);
StaxUtil.writeCData(sw, buffer, offset, len);
} else {
sw.writeCharacters(buffer, offset, len);
}
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/tools/jackson/dataformat/xml/util/StaxUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

import javax.xml.stream.*;

import org.codehaus.stax2.XMLStreamWriter2;

import tools.jackson.core.*;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.core.exc.StreamWriteException;
Expand Down Expand Up @@ -36,6 +38,46 @@ private static Throwable _unwrap(Throwable t) {
return t;
}

/**
* Writes {@code text} as one or more CDATA sections. XML does not allow the
* sequence {@code "]]>"} inside a CDATA block, so where it occurs the value is
* split: the {@code "]]"} ends one section and the {@code ">"} starts the next.
* A coalescing reader (the default for this module) reads the pieces back as
* the original text, so a value containing {@code "]]>"} round-trips instead of
* making the underlying Stax writer reject it.
*/
public static void writeCData(XMLStreamWriter2 sw, String text)
throws XMLStreamException
{
int ix = text.indexOf("]]>");
if (ix < 0) {
sw.writeCData(text);
return;
}
int start = 0;
do {
// keep the "]]" in this section, push the ">" into the next one
sw.writeCData(text.substring(start, ix + 2));
start = ix + 2;
ix = text.indexOf("]]>", start);
} while (ix >= 0);
sw.writeCData(text.substring(start));
}

public static void writeCData(XMLStreamWriter2 sw, char[] buffer, int offset, int len)
throws XMLStreamException
{
// Common case has no "]]>" in range, so avoid allocating a String for it
final int end = offset + len;
for (int i = offset; i < end - 2; ++i) {
if (buffer[i] == ']' && buffer[i + 1] == ']' && buffer[i + 2] == '>') {
writeCData(sw, new String(buffer, offset, len));
return;
}
}
sw.writeCData(buffer, offset, len);
}

private static String _message(Throwable t1, Throwable t2) {
String msg = t1.getMessage();
if (msg == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package tools.jackson.dataformat.xml.ser;

import org.junit.jupiter.api.Test;

import tools.jackson.dataformat.xml.XmlMapper;
import tools.jackson.dataformat.xml.XmlTestUtil;
import tools.jackson.dataformat.xml.annotation.JacksonXmlCData;

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

// Values written as CDATA must survive serialization even when they contain
// the "]]>" sequence that closes a CDATA section.
public class CDataEndMarkerSerTest extends XmlTestUtil
{
static class CDataBean
{
@JacksonXmlCData
public String value;

public CDataBean() { }
public CDataBean(String v) { value = v; }
}

private final XmlMapper MAPPER = newMapper();

@Test
public void testCDataWithEndMarkerRoundTrip() throws Exception
{
_roundTrip("a]]>b");
_roundTrip("]]>");
_roundTrip("]]>]]>");
_roundTrip("trailing]]>");
_roundTrip("<![CDATA[nested]]>tail");
}

@Test
public void testCDataWithoutEndMarkerUnchanged() throws Exception
{
String xml = MAPPER.writeValueAsString(new CDataBean("plain"));
assertEquals("<CDataBean><value><![CDATA[plain]]></value></CDataBean>", xml);
}

private void _roundTrip(String value) throws Exception
{
String xml = MAPPER.writeValueAsString(new CDataBean(value));
CDataBean result = MAPPER.readValue(xml, CDataBean.class);
assertEquals(value, result.value, "round-trip failed for XML: " + xml);
}
}
Loading