diff --git a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/AbstractEncodedAppender.java b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/AbstractEncodedAppender.java index 4693c93be53..bf8254093e0 100644 --- a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/AbstractEncodedAppender.java +++ b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/AbstractEncodedAppender.java @@ -24,6 +24,8 @@ * @since 2.3 */ public abstract class AbstractEncodedAppender implements EncodedAppender { + private boolean ignoreEncodingState; + /** * Append a portion of a char array to the buffer and attach the * encodingState information to it @@ -172,9 +174,9 @@ public void appendEncoded(Encoder encoder, EncodingState encodingState, CharSequ * @return true, if should encode */ protected boolean shouldEncode(Encoder encoderToApply, EncodingState encodingState) { - return encoderToApply != null + return ignoreEncodingState || (encoderToApply != null && (encodingState == null || DefaultEncodingStateRegistry.shouldEncodeWith(encoderToApply, - encodingState)); + encodingState))); } /** @@ -272,4 +274,12 @@ public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) { System.arraycopy(chars, start + srcBegin, dst, dstBegin, srcEnd - srcBegin); } } + + public boolean isIgnoreEncodingState() { + return ignoreEncodingState; + } + + public void setIgnoreEncodingState(boolean ignoreEncodingState) { + this.ignoreEncodingState = ignoreEncodingState; + } } diff --git a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/EncodedAppender.java b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/EncodedAppender.java index 2be9103b7ef..313e83bb4fe 100644 --- a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/EncodedAppender.java +++ b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/support/encoding/EncodedAppender.java @@ -125,4 +125,18 @@ void appendEncoded(Encoder encoder, EncodingState encodingState, CharSequence st void flush() throws IOException; public void close() throws IOException; + + + /** + * When enabled, will encode all input regardless of it's current state + * disables double-encoding prevention. + * + * @param ignoreEncodingState + */ + void setIgnoreEncodingState(boolean ignoreEncodingState); + + /** + * @return current state of ignoreEncodingState setting + */ + public boolean isIgnoreEncodingState(); } diff --git a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/CodecPrintWriter.java b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/CodecPrintWriter.java index 9dd77481408..aa1e5319b6e 100644 --- a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/CodecPrintWriter.java +++ b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/CodecPrintWriter.java @@ -26,8 +26,13 @@ public class CodecPrintWriter extends GrailsPrintWriter implements EncoderAware, EncodedAppenderFactory { private final Encoder encoder; private final StreamCharBuffer buffer; + private boolean ignoreEncodingState; public CodecPrintWriter(Writer out, Encoder encoder, EncodingStateRegistry encodingStateRegistry) { + this(out, encoder, encodingStateRegistry, false); + } + + public CodecPrintWriter(Writer out, Encoder encoder, EncodingStateRegistry encodingStateRegistry, boolean ignoreEncodingState) { super(null); this.encoder = encoder; buffer=new StreamCharBuffer(); @@ -38,7 +43,7 @@ public CodecPrintWriter(Writer out, Encoder encoder, EncodingStateRegistry encod buffer.setWriteDirectlyToConnectedMinSize(0); buffer.setChunkMinSize(0); } - setOut(buffer.getWriterForEncoder(encoder, encodingStateRegistry)); + setOut(buffer.getWriterForEncoder(encoder, encodingStateRegistry, ignoreEncodingState)); } public Encoder getEncoder() { @@ -46,11 +51,13 @@ public Encoder getEncoder() { } public EncodedAppender getEncodedAppender() { - return ((EncodedAppenderFactory)buffer.getWriter()).getEncodedAppender(); + EncodedAppender encodedAppender = ((EncodedAppenderFactory)buffer.getWriter()).getEncodedAppender(); + encodedAppender.setIgnoreEncodingState(ignoreEncodingState); + return encodedAppender; } @Override public Writer getWriterForEncoder(Encoder encoder, EncodingStateRegistry encodingStateRegistry) { - return buffer.getWriterForEncoder(encoder, encodingStateRegistry); + return buffer.getWriterForEncoder(encoder, encodingStateRegistry, ignoreEncodingState); } } diff --git a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/StreamCharBuffer.java b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/StreamCharBuffer.java index 3f848ad9892..9071b105e4d 100644 --- a/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/StreamCharBuffer.java +++ b/grails-encoder/src/main/groovy/org/codehaus/groovy/grails/web/util/StreamCharBuffer.java @@ -2623,11 +2623,17 @@ public Writer getWriterForEncoder() { public Writer getWriterForEncoder(Encoder encoder) { EncodingStateRegistryLookup encodingStateRegistryLookup = EncodingStateRegistryLookupHolder.getEncodingStateRegistryLookup(); EncodingStateRegistry encodingStateRegistry = encodingStateRegistryLookup != null ? encodingStateRegistryLookup.lookup() : null; - return getWriterForEncoder(encoder, encodingStateRegistry); + return getWriterForEncoder(encoder, encodingStateRegistry, false); } public Writer getWriterForEncoder(Encoder encoder, EncodingStateRegistry encodingStateRegistry) { - return new EncodedAppenderWriter(writer.getEncodedAppender(), encoder, encodingStateRegistry); + return getWriterForEncoder(encoder, encodingStateRegistry, false); + } + + public Writer getWriterForEncoder(Encoder encoder, EncodingStateRegistry encodingStateRegistry, boolean ignoreEncodingState) { + EncodedAppender encodedAppender = writer.getEncodedAppender(); + encodedAppender.setIgnoreEncodingState(ignoreEncodingState); + return new EncodedAppenderWriter(encodedAppender, encoder, encodingStateRegistry); } public boolean isNotifyParentBuffersEnabled() { diff --git a/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/codecs/CodecSpec.groovy b/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/codecs/CodecSpec.groovy index 56848618bac..52efe2e9415 100644 --- a/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/codecs/CodecSpec.groovy +++ b/grails-test-suite-web/src/test/groovy/org/codehaus/groovy/grails/web/codecs/CodecSpec.groovy @@ -1,8 +1,12 @@ package org.codehaus.groovy.grails.web.codecs import grails.converters.XML +import grails.converters.JSON import grails.test.mixin.TestMixin import grails.test.mixin.web.GroovyPageUnitTestMixin + +import org.codehaus.groovy.grails.web.util.StreamCharBuffer + import spock.lang.Issue import spock.lang.Specification @@ -93,6 +97,18 @@ class CodecSpec extends Specification { public boolean equals(Object obj) { throw new RuntimeException("equals shouldn't be called") } + } + + @Issue("GRAILS-11361") + void "JSON converter should not use encoding state"() { + given: + def buffer=new StreamCharBuffer() + buffer.writer.write('"Hello world"') + def content=buffer.encodeAsRaw() + when: + def json = [content: content] as JSON + then: + json.toString() == '{"content":"\\"Hello world\\""}' } void "output should be safe at the end"() { diff --git a/grails-web-common/src/main/groovy/org/codehaus/groovy/grails/web/json/JSONObject.java b/grails-web-common/src/main/groovy/org/codehaus/groovy/grails/web/json/JSONObject.java index a21ab7ac126..da95dc4fc75 100644 --- a/grails-web-common/src/main/groovy/org/codehaus/groovy/grails/web/json/JSONObject.java +++ b/grails-web-common/src/main/groovy/org/codehaus/groovy/grails/web/json/JSONObject.java @@ -1129,7 +1129,7 @@ static void writeQuoted(Writer writer, Object value) throws IOException { javascriptEncoderStateless.encodeToWriter((CharSequence)value, writer); } else { - CodecPrintWriter codecWriter = new CodecPrintWriter(writer, javascriptEncoder, null); + CodecPrintWriter codecWriter = new CodecPrintWriter(writer, javascriptEncoder, null, true); codecWriter.print(value); codecWriter.flush(); }