diff --git a/src/main/java/com/fasterxml/jackson/databind/util/JSONPObject.java b/src/main/java/com/fasterxml/jackson/databind/util/JSONPObject.java index 3379d401b9..b82884e88a 100644 --- a/src/main/java/com/fasterxml/jackson/databind/util/JSONPObject.java +++ b/src/main/java/com/fasterxml/jackson/databind/util/JSONPObject.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.*; +import com.fasterxml.jackson.core.io.CharacterEscapes; import com.fasterxml.jackson.databind.*; import com.fasterxml.jackson.databind.jsontype.TypeSerializer; @@ -66,18 +67,31 @@ public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, T public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { - // First, wrapping: - jgen.writeRaw(_function); - jgen.writeRaw('('); - if (_value == null) { - provider.defaultSerializeNull(jgen); - } else if (_serializationType != null) { - provider.findTypedValueSerializer(_serializationType, true, null).serialize(_value, jgen, provider); - } else { - Class cls = _value.getClass(); - provider.findTypedValueSerializer(cls, true, null).serialize(_value, jgen, provider); + CharacterEscapes currentCharacterEscapes = jgen.getCharacterEscapes(); + + // NOTE: Escape line-separator characters that break JSONP only if no custom character escapes are set. + // If custom escapes are in place JSONP-breaking characters will not be escaped and it is recommended to + // add escaping for those (see JsonpCharacterEscapes class). + if (currentCharacterEscapes == null) { + jgen.setCharacterEscapes(JsonpCharacterEscapes.instance()); + } + + try { + // First, wrapping: + jgen.writeRaw(_function); + jgen.writeRaw('('); + if (_value == null) { + provider.defaultSerializeNull(jgen); + } else if (_serializationType != null) { + provider.findTypedValueSerializer(_serializationType, true, null).serialize(_value, jgen, provider); + } else { + Class cls = _value.getClass(); + provider.findTypedValueSerializer(cls, true, null).serialize(_value, jgen, provider); + } + jgen.writeRaw(')'); + } finally { + jgen.setCharacterEscapes(currentCharacterEscapes); } - jgen.writeRaw(')'); } /* diff --git a/src/test/java/com/fasterxml/jackson/databind/util/JSONPObjectTest.java b/src/test/java/com/fasterxml/jackson/databind/util/JSONPObjectTest.java new file mode 100644 index 0000000000..032f40bc7e --- /dev/null +++ b/src/test/java/com/fasterxml/jackson/databind/util/JSONPObjectTest.java @@ -0,0 +1,37 @@ +package com.fasterxml.jackson.databind.util; + +import java.io.IOException; + +import com.fasterxml.jackson.databind.BaseMapTest; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class JSONPObjectTest extends BaseMapTest { + + private final String CALLBACK = "callback"; + private final ObjectMapper MAPPER = new ObjectMapper(); + + /** + * Unit tests for checking that JSONP breaking characters U+2028 and U+2029 are escaped when creating a {@link JSONPObject}. + */ + + public void testU2028Escaped() throws IOException { + String containsU2028 = String.format("This string contains %c char", '\u2028'); + JSONPObject jsonpObject = new JSONPObject(CALLBACK, containsU2028); + String valueAsString = MAPPER.writeValueAsString(jsonpObject); + assertFalse(valueAsString.contains("\u2028")); + } + + public void testU2029Escaped() throws IOException { + String containsU2029 = String.format("This string contains %c char", '\u2029'); + JSONPObject jsonpObject = new JSONPObject(CALLBACK, containsU2029); + String valueAsString = MAPPER.writeValueAsString(jsonpObject); + assertFalse(valueAsString.contains("\u2029")); + } + + public void testU2030NotEscaped() throws IOException { + String containsU2030 = String.format("This string contains %c char", '\u2030'); + JSONPObject jsonpObject = new JSONPObject(CALLBACK, containsU2030); + String valueAsString = MAPPER.writeValueAsString(jsonpObject); + assertTrue(valueAsString.contains("\u2030")); + } +}