From 96ee8a3bc71815700c5e952d5eaac733415bfeda Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Wed, 28 Jul 2021 16:08:28 +0200 Subject: [PATCH] Ensure characterEncoding in MockHttpServletResponse is non-null Closes gh-27219 --- .../mock/web/MockHttpServletResponse.java | 44 ++++++++++--------- .../servlet/result/PrintingResultHandler.java | 7 +-- .../web/MockHttpServletResponseTests.java | 4 ++ .../result/PrintingResultHandlerTests.java | 12 +---- .../servlet/MockHttpServletResponse.java | 44 ++++++++++--------- 5 files changed, 53 insertions(+), 58 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index 4182597f33de..a3140793791e 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -80,7 +80,6 @@ public class MockHttpServletResponse implements HttpServletResponse { private boolean writerAccessAllowed = true; - @Nullable private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; /** @@ -171,8 +170,8 @@ public boolean isWriterAccessAllowed() { * Determine whether the character encoding has been explicitly set through * {@link HttpServletResponse} methods or through a {@code charset} parameter * on the {@code Content-Type}. - *

If {@code false}, {@link #getCharacterEncoding()} will return a default - * encoding value. + *

If {@code false}, {@link #getCharacterEncoding()} will return the default + * character encoding. */ public boolean isCharset() { return this.characterEncodingSet; @@ -180,16 +179,21 @@ public boolean isCharset() { @Override public void setCharacterEncoding(String characterEncoding) { + setExplicitCharacterEncoding(characterEncoding); + updateContentTypePropertyAndHeader(); + } + + private void setExplicitCharacterEncoding(String characterEncoding) { + Assert.notNull(characterEncoding, "'characterEncoding' must not be null"); this.characterEncoding = characterEncoding; this.characterEncodingSet = true; - updateContentTypePropertyAndHeader(); } private void updateContentTypePropertyAndHeader() { if (this.contentType != null) { String value = this.contentType; - if (this.characterEncodingSet && !this.contentType.toLowerCase().contains(CHARSET_PREFIX)) { - value = value + ';' + CHARSET_PREFIX + this.characterEncoding; + if (this.characterEncodingSet && !value.toLowerCase().contains(CHARSET_PREFIX)) { + value += ';' + CHARSET_PREFIX + getCharacterEncoding(); this.contentType = value; } doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true); @@ -197,7 +201,6 @@ private void updateContentTypePropertyAndHeader() { } @Override - @Nullable public String getCharacterEncoding() { return this.characterEncoding; } @@ -212,9 +215,7 @@ public ServletOutputStream getOutputStream() { public PrintWriter getWriter() throws UnsupportedEncodingException { Assert.state(this.writerAccessAllowed, "Writer access not allowed"); if (this.writer == null) { - Writer targetWriter = (this.characterEncoding != null ? - new OutputStreamWriter(this.content, this.characterEncoding) : - new OutputStreamWriter(this.content)); + Writer targetWriter = new OutputStreamWriter(this.content, getCharacterEncoding()); this.writer = new ResponsePrintWriter(targetWriter); } return this.writer; @@ -228,14 +229,16 @@ public byte[] getContentAsByteArray() { * Get the content of the response body as a {@code String}, using the charset * specified for the response by the application, either through * {@link HttpServletResponse} methods or through a charset parameter on the - * {@code Content-Type}. + * {@code Content-Type}. If no charset has been explicitly defined, the default + * character encoding will be used. * @return the content as a {@code String} * @throws UnsupportedEncodingException if the character encoding is not supported * @see #getContentAsString(Charset) + * @see #setCharacterEncoding(String) + * @see #setContentType(String) */ public String getContentAsString() throws UnsupportedEncodingException { - return (this.characterEncoding != null ? - this.content.toString(this.characterEncoding) : this.content.toString()); + return this.content.toString(getCharacterEncoding()); } /** @@ -248,11 +251,12 @@ public String getContentAsString() throws UnsupportedEncodingException { * @throws UnsupportedEncodingException if the character encoding is not supported * @since 5.2 * @see #getContentAsString() + * @see #setCharacterEncoding(String) + * @see #setContentType(String) */ public String getContentAsString(Charset fallbackCharset) throws UnsupportedEncodingException { - return (isCharset() && this.characterEncoding != null ? - this.content.toString(this.characterEncoding) : - this.content.toString(fallbackCharset.name())); + String charsetName = (this.characterEncodingSet ? getCharacterEncoding() : fallbackCharset.name()); + return this.content.toString(charsetName); } @Override @@ -282,16 +286,14 @@ public void setContentType(@Nullable String contentType) { try { MediaType mediaType = MediaType.parseMediaType(contentType); if (mediaType.getCharset() != null) { - this.characterEncoding = mediaType.getCharset().name(); - this.characterEncodingSet = true; + setExplicitCharacterEncoding(mediaType.getCharset().name()); } } catch (Exception ex) { // Try to get charset value anyway int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); if (charsetIndex != -1) { - this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); - this.characterEncodingSet = true; + setExplicitCharacterEncoding(contentType.substring(charsetIndex + CHARSET_PREFIX.length())); } } updateContentTypePropertyAndHeader(); @@ -344,7 +346,7 @@ public boolean isCommitted() { @Override public void reset() { resetBuffer(); - this.characterEncoding = null; + this.characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; this.characterEncodingSet = false; this.contentLength = 0; this.contentType = null; diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/result/PrintingResultHandler.java b/spring-test/src/main/java/org/springframework/test/web/servlet/result/PrintingResultHandler.java index a358760aa3e2..faaae9806db9 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/result/PrintingResultHandler.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/result/PrintingResultHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -248,14 +248,11 @@ protected void printFlashMap(FlashMap flashMap) throws Exception { * Print the response. */ protected void printResponse(MockHttpServletResponse response) throws Exception { - String body = (response.getCharacterEncoding() != null ? - response.getContentAsString() : MISSING_CHARACTER_ENCODING); - this.printer.printValue("Status", response.getStatus()); this.printer.printValue("Error message", response.getErrorMessage()); this.printer.printValue("Headers", getResponseHeaders(response)); this.printer.printValue("Content type", response.getContentType()); - this.printer.printValue("Body", body); + this.printer.printValue("Body", response.getContentAsString()); this.printer.printValue("Forwarded URL", response.getForwardedUrl()); this.printer.printValue("Redirected URL", response.getRedirectedUrl()); printCookies(response.getCookies()); diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index 1b45d2d36c2a..e8ac11b36b3e 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -551,6 +551,8 @@ private void assertPrimarySessionCookie(String expectedValue) { @Test // gh-25501 void resetResetsCharset() { + assertThat(response.getContentType()).isNull(); + assertThat(response.getCharacterEncoding()).isEqualTo("ISO-8859-1"); assertThat(response.isCharset()).isFalse(); response.setCharacterEncoding("UTF-8"); assertThat(response.isCharset()).isTrue(); @@ -562,6 +564,8 @@ void resetResetsCharset() { response.reset(); + assertThat(response.getContentType()).isNull(); + assertThat(response.getCharacterEncoding()).isEqualTo("ISO-8859-1"); assertThat(response.isCharset()).isFalse(); // Do not invoke setCharacterEncoding() since that sets the charset flag to true. // response.setCharacterEncoding("UTF-8"); diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java index b97f8d3d1f64..f91c39fda969 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/result/PrintingResultHandlerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -238,16 +238,6 @@ public void printResponseWithDefaultCharacterEncoding() throws Exception { assertValue("MockHttpServletResponse", "Body", "text"); } - @Test - public void printResponseWithoutCharacterEncoding() throws Exception { - this.response.setCharacterEncoding(null); - this.response.getWriter().print("text"); - - this.handler.handle(this.mvcResult); - - assertValue("MockHttpServletResponse", "Body", ""); - } - @Test public void printHandlerNull() throws Exception { StubMvcResult mvcResult = new StubMvcResult(this.request, null, null, null, null, null, this.response); diff --git a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java index e357bc3ce191..fc294fe2b643 100644 --- a/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java +++ b/spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletResponse.java @@ -80,7 +80,6 @@ public class MockHttpServletResponse implements HttpServletResponse { private boolean writerAccessAllowed = true; - @Nullable private String characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; /** @@ -171,8 +170,8 @@ public boolean isWriterAccessAllowed() { * Determine whether the character encoding has been explicitly set through * {@link HttpServletResponse} methods or through a {@code charset} parameter * on the {@code Content-Type}. - *

If {@code false}, {@link #getCharacterEncoding()} will return a default - * encoding value. + *

If {@code false}, {@link #getCharacterEncoding()} will return the default + * character encoding. */ public boolean isCharset() { return this.characterEncodingSet; @@ -180,16 +179,21 @@ public boolean isCharset() { @Override public void setCharacterEncoding(String characterEncoding) { + setExplicitCharacterEncoding(characterEncoding); + updateContentTypePropertyAndHeader(); + } + + private void setExplicitCharacterEncoding(String characterEncoding) { + Assert.notNull(characterEncoding, "'characterEncoding' must not be null"); this.characterEncoding = characterEncoding; this.characterEncodingSet = true; - updateContentTypePropertyAndHeader(); } private void updateContentTypePropertyAndHeader() { if (this.contentType != null) { String value = this.contentType; - if (this.characterEncodingSet && !this.contentType.toLowerCase().contains(CHARSET_PREFIX)) { - value = value + ';' + CHARSET_PREFIX + this.characterEncoding; + if (this.characterEncodingSet && !value.toLowerCase().contains(CHARSET_PREFIX)) { + value += ';' + CHARSET_PREFIX + getCharacterEncoding(); this.contentType = value; } doAddHeaderValue(HttpHeaders.CONTENT_TYPE, value, true); @@ -197,7 +201,6 @@ private void updateContentTypePropertyAndHeader() { } @Override - @Nullable public String getCharacterEncoding() { return this.characterEncoding; } @@ -212,9 +215,7 @@ public ServletOutputStream getOutputStream() { public PrintWriter getWriter() throws UnsupportedEncodingException { Assert.state(this.writerAccessAllowed, "Writer access not allowed"); if (this.writer == null) { - Writer targetWriter = (this.characterEncoding != null ? - new OutputStreamWriter(this.content, this.characterEncoding) : - new OutputStreamWriter(this.content)); + Writer targetWriter = new OutputStreamWriter(this.content, getCharacterEncoding()); this.writer = new ResponsePrintWriter(targetWriter); } return this.writer; @@ -228,14 +229,16 @@ public byte[] getContentAsByteArray() { * Get the content of the response body as a {@code String}, using the charset * specified for the response by the application, either through * {@link HttpServletResponse} methods or through a charset parameter on the - * {@code Content-Type}. + * {@code Content-Type}. If no charset has been explicitly defined, the default + * character encoding will be used. * @return the content as a {@code String} * @throws UnsupportedEncodingException if the character encoding is not supported * @see #getContentAsString(Charset) + * @see #setCharacterEncoding(String) + * @see #setContentType(String) */ public String getContentAsString() throws UnsupportedEncodingException { - return (this.characterEncoding != null ? - this.content.toString(this.characterEncoding) : this.content.toString()); + return this.content.toString(getCharacterEncoding()); } /** @@ -248,11 +251,12 @@ public String getContentAsString() throws UnsupportedEncodingException { * @throws UnsupportedEncodingException if the character encoding is not supported * @since 5.2 * @see #getContentAsString() + * @see #setCharacterEncoding(String) + * @see #setContentType(String) */ public String getContentAsString(Charset fallbackCharset) throws UnsupportedEncodingException { - return (isCharset() && this.characterEncoding != null ? - this.content.toString(this.characterEncoding) : - this.content.toString(fallbackCharset.name())); + String charsetName = (this.characterEncodingSet ? getCharacterEncoding() : fallbackCharset.name()); + return this.content.toString(charsetName); } @Override @@ -282,16 +286,14 @@ public void setContentType(@Nullable String contentType) { try { MediaType mediaType = MediaType.parseMediaType(contentType); if (mediaType.getCharset() != null) { - this.characterEncoding = mediaType.getCharset().name(); - this.characterEncodingSet = true; + setExplicitCharacterEncoding(mediaType.getCharset().name()); } } catch (Exception ex) { // Try to get charset value anyway int charsetIndex = contentType.toLowerCase().indexOf(CHARSET_PREFIX); if (charsetIndex != -1) { - this.characterEncoding = contentType.substring(charsetIndex + CHARSET_PREFIX.length()); - this.characterEncodingSet = true; + setExplicitCharacterEncoding(contentType.substring(charsetIndex + CHARSET_PREFIX.length())); } } updateContentTypePropertyAndHeader(); @@ -344,7 +346,7 @@ public boolean isCommitted() { @Override public void reset() { resetBuffer(); - this.characterEncoding = null; + this.characterEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING; this.characterEncodingSet = false; this.contentLength = 0; this.contentType = null;