This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/master by this push:
     new 5e974d6  Fix BZ 64938 Clarify expected behaviour of 
setCharacterEncoding(null)
5e974d6 is described below

commit 5e974d66cd4edcb9f18207d90b2ecd433104ec06
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Tue Feb 23 12:49:59 2021 +0000

    Fix BZ 64938 Clarify expected behaviour of setCharacterEncoding(null)
    
    Also covers setContentType(null) and setLocale(null)
    https://bz.apache.org/bugzilla/show_bug.cgi?id=64938
---
 .../apache/catalina/connector/OutputBuffer.java    |   6 +
 java/org/apache/catalina/connector/Response.java   |  40 ++-
 java/org/apache/coyote/Response.java               |   9 +-
 .../apache/catalina/connector/TestResponse.java    | 343 +++++++++++++++++++++
 test/org/apache/tomcat/unittest/TesterContext.java |  23 +-
 webapps/docs/changelog.xml                         |   7 +
 6 files changed, 408 insertions(+), 20 deletions(-)

diff --git a/java/org/apache/catalina/connector/OutputBuffer.java 
b/java/org/apache/catalina/connector/OutputBuffer.java
index 59939ef..ac47555 100644
--- a/java/org/apache/catalina/connector/OutputBuffer.java
+++ b/java/org/apache/catalina/connector/OutputBuffer.java
@@ -35,6 +35,7 @@ import org.apache.catalina.Globals;
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.CloseNowException;
 import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.C2BConverter;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -560,6 +561,11 @@ public class OutputBuffer extends Writer {
         }
 
         if (charset == null) {
+            if (coyoteResponse.getCharacterEncoding() != null) {
+                // setCharacterEncoding() was called with an invalid character 
set
+                // Trigger an UnsupportedEncodingException
+                charset = 
B2CConverter.getCharset(coyoteResponse.getCharacterEncoding());
+            }
             charset = org.apache.coyote.Constants.DEFAULT_BODY_CHARSET;
         }
 
diff --git a/java/org/apache/catalina/connector/Response.java 
b/java/org/apache/catalina/connector/Response.java
index 783772a..d6c0e92 100644
--- a/java/org/apache/catalina/connector/Response.java
+++ b/java/org/apache/catalina/connector/Response.java
@@ -724,6 +724,12 @@ public class Response implements HttpServletResponse {
 
         if (type == null) {
             getCoyoteResponse().setContentType(null);
+            try {
+                getCoyoteResponse().setCharacterEncoding(null);
+            } catch (UnsupportedEncodingException e) {
+                // Can never happen when calling with null
+            }
+            isCharacterEncodingSet = false;
             return;
         }
 
@@ -791,7 +797,11 @@ public class Response implements HttpServletResponse {
             log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), 
e);
             return;
         }
-        isCharacterEncodingSet = true;
+        if (charset == null) {
+            isCharacterEncodingSet = false;
+        } else {
+            isCharacterEncodingSet = true;
+        }
     }
 
 
@@ -825,16 +835,24 @@ public class Response implements HttpServletResponse {
             return;
         }
 
-        // In some error handling scenarios, the context is unknown
-        // (e.g. a 404 when a ROOT context is not present)
-        Context context = getContext();
-        if (context != null) {
-            String charset = context.getCharset(locale);
-            if (charset != null) {
-                try {
-                    getCoyoteResponse().setCharacterEncoding(charset);
-                } catch (UnsupportedEncodingException e) {
-                    log.warn(sm.getString("coyoteResponse.encoding.invalid", 
charset), e);
+        if (locale == null) {
+            try {
+                getCoyoteResponse().setCharacterEncoding(null);
+            } catch (UnsupportedEncodingException e) {
+                // Impossible when calling with null
+            }
+        } else {
+            // In some error handling scenarios, the context is unknown
+            // (e.g. a 404 when a ROOT context is not present)
+            Context context = getContext();
+            if (context != null) {
+                String charset = context.getCharset(locale);
+                if (charset != null) {
+                    try {
+                        getCoyoteResponse().setCharacterEncoding(charset);
+                    } catch (UnsupportedEncodingException e) {
+                        
log.warn(sm.getString("coyoteResponse.encoding.invalid", charset), e);
+                    }
                 }
             }
         }
diff --git a/java/org/apache/coyote/Response.java 
b/java/org/apache/coyote/Response.java
index 91473a1..d5f95ea 100644
--- a/java/org/apache/coyote/Response.java
+++ b/java/org/apache/coyote/Response.java
@@ -465,8 +465,9 @@ public final class Response {
     public void setLocale(Locale locale) {
 
         if (locale == null) {
-            return;  // throw an exception?
-        }
+            this.locale = null;
+            this.contentLanguage = null;
+            return;        }
 
         // Save the locale for use by getLocale()
         this.locale = locale;
@@ -500,11 +501,13 @@ public final class Response {
             return;
         }
         if (characterEncoding == null) {
+            this.charset = null;
+            this.characterEncoding = null;
             return;
         }
 
-        this.charset = B2CConverter.getCharset(characterEncoding);
         this.characterEncoding = characterEncoding;
+        this.charset = B2CConverter.getCharset(characterEncoding);
     }
 
 
diff --git a/test/org/apache/catalina/connector/TestResponse.java 
b/test/org/apache/catalina/connector/TestResponse.java
index 55fb014..c24bc44 100644
--- a/test/org/apache/catalina/connector/TestResponse.java
+++ b/test/org/apache/catalina/connector/TestResponse.java
@@ -19,7 +19,10 @@ package org.apache.catalina.connector;
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import jakarta.servlet.ServletException;
@@ -636,6 +639,346 @@ public class TestResponse extends TomcatBaseTest {
     }
 
 
+    private static final String ISO_8859_1 = 
StandardCharsets.ISO_8859_1.name();
+    private static final String UTF_8 = StandardCharsets.UTF_8.name();
+    private static final String UNKNOWN = "unknown";
+    private static final String TEXT = "text/plain";
+    private static final String TEXT_ISO_8859_1 = TEXT + ";charset=" + 
ISO_8859_1;
+    private static final String TEXT_UTF_8 = TEXT + ";charset=" + UTF_8;
+    private static final String TEXT_UNKNOWN = TEXT + ";charset=" + UNKNOWN;
+    private static final Locale UNDETERMINED = new Locale("und");
+
+    @Test
+    public void testSetCharacterEncoding01() {
+        Response response = setupResponse();
+
+        // Check default
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding02() {
+        Response response = setupResponse();
+
+        // Check multiple calls
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setCharacterEncoding(ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding03() throws IOException {
+        Response response = setupResponse();
+
+        // Check after getWriter()
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.getWriter();
+        response.setCharacterEncoding(ISO_8859_1);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding04() throws IOException {
+        Response response = setupResponse();
+
+        // Check after commit
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.flushBuffer();
+        response.setCharacterEncoding(ISO_8859_1);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding05() {
+        Response response = setupResponse();
+
+        // Check calling with null
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setCharacterEncoding(null);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test(expected =  UnsupportedEncodingException.class)
+    public void testSetCharacterEncoding06() throws IOException {
+        Response response = setupResponse();
+
+        // Check calling with an unknown character set and writer
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UNKNOWN);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getWriter();
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding07() throws IOException {
+        Response response = setupResponse();
+
+        // Check calling with an unknown character set
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UNKNOWN);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getOutputStream();
+    }
+
+
+    @Test
+    public void testSetCharacterEncoding08() {
+        Response response = setupResponse();
+
+        // Check multiple calls with different methods
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setContentType(TEXT_ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setCharacterEncoding(ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetContentType01() {
+        Response response = setupResponse();
+
+        // Check multiple calls
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setContentType(TEXT_ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetContentType02() throws IOException {
+        Response response = setupResponse();
+
+        // Check after getWriter()
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.getWriter();
+        response.setContentType(TEXT_ISO_8859_1);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetContentType03() throws IOException {
+        Response response = setupResponse();
+
+        // Check after commit
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.flushBuffer();
+        response.setContentType(TEXT_ISO_8859_1);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetContentType04() {
+        Response response = setupResponse();
+
+        // Check calling with null
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setContentType(null);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test(expected =  UnsupportedEncodingException.class)
+    public void testSetContentType05() throws IOException {
+        Response response = setupResponse();
+        
response.getContext().addLocaleEncodingMappingParameter(Locale.UK.toLanguageTag(),
 UNKNOWN);
+
+        // Check calling with an unknown character set and writer
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UNKNOWN);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getWriter();
+    }
+
+
+    @Test
+    public void testSetContentType06() throws IOException {
+        Response response = setupResponse();
+
+        // Check calling with an unknown character set
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setContentType(TEXT_UNKNOWN);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getOutputStream();
+    }
+
+
+    @Test
+    public void testSetLocale01() {
+        Response response = setupResponse();
+
+        // Check multiple calls
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setLocale(Locale.ENGLISH);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetLocale02() throws IOException {
+        Response response = setupResponse();
+
+        // Check after getWriter()
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.getWriter();
+        response.setLocale(Locale.ENGLISH);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetLocale03() throws IOException {
+        Response response = setupResponse();
+
+        // Check after commit
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.flushBuffer();
+        response.setLocale(Locale.ENGLISH);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetLocale04() {
+        Response response = setupResponse();
+
+        // Check calling with null
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setLocale(null);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test(expected =  UnsupportedEncodingException.class)
+    public void testSetLocale05() throws IOException {
+        Response response = setupResponse();
+
+        // Check calling with an unknown character set and writer
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(UNDETERMINED);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getWriter();
+    }
+
+
+    @Test
+    public void testSetLocale06() throws IOException {
+        Response response = setupResponse();
+
+        // Check calling with an unknown character set
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+        response.setLocale(UNDETERMINED);
+        Assert.assertEquals(UNKNOWN, response.getCharacterEncoding());
+        response.getOutputStream();
+    }
+
+
+    @Test
+    public void testSetLocale07() {
+        Response response = setupResponse();
+
+        // Check setLocale() is over-ridden by setCharacterEncoding
+
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+
+        // setLocale doesn't change previous value
+        response.setCharacterEncoding(UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setLocale(Locale.ENGLISH);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+
+        // Reset
+        response.setCharacterEncoding(null);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+
+        // setLocale is over-ridden by setCharacterEncoding
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setCharacterEncoding(ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    @Test
+    public void testSetLocale08() {
+        Response response = setupResponse();
+
+        // Check setLocale() is over-ridden by setContentType
+
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+
+        // setLocale doesn't change previous value
+        response.setContentType(TEXT_UTF_8);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setLocale(Locale.ENGLISH);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+
+        // Reset
+        response.setContentType(null);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+
+        // setLocale is over-ridden by setContentTpe
+        response.setLocale(Locale.CHINESE);
+        Assert.assertEquals(UTF_8, response.getCharacterEncoding());
+        response.setContentType(TEXT_ISO_8859_1);
+        Assert.assertEquals(ISO_8859_1, response.getCharacterEncoding());
+    }
+
+
+    private Response setupResponse() {
+        Connector connector = new Connector();
+        org.apache.coyote.Response cResponse = new 
org.apache.coyote.Response();
+        Response response = new Response();
+        response.setCoyoteResponse(cResponse);
+        Request request = new Request(connector);
+        org.apache.coyote.Request cRequest = new org.apache.coyote.Request();
+        request.setCoyoteRequest(cRequest);
+        Context context = new TesterContext();
+        request.getMappingData().context = context;
+        response.setRequest(request);
+        
context.addLocaleEncodingMappingParameter(Locale.ENGLISH.getLanguage(), 
ISO_8859_1);
+        
context.addLocaleEncodingMappingParameter(Locale.CHINESE.getLanguage(), UTF_8);
+        
context.addLocaleEncodingMappingParameter(UNDETERMINED.toLanguageTag(), 
UNKNOWN);
+        return response;
+    }
+
+
     private static final class Bug52811Servlet extends HttpServlet {
         private static final long serialVersionUID = 1L;
 
diff --git a/test/org/apache/tomcat/unittest/TesterContext.java 
b/test/org/apache/tomcat/unittest/TesterContext.java
index 4e0e362..7164bcc 100644
--- a/test/org/apache/tomcat/unittest/TesterContext.java
+++ b/test/org/apache/tomcat/unittest/TesterContext.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 import javax.management.ObjectName;
 
@@ -365,11 +366,6 @@ public class TesterContext implements Context {
     }
 
     @Override
-    public String getCharset(Locale locale) {
-        return null;
-    }
-
-    @Override
     public URL getConfigFile() {
         return null;
     }
@@ -739,9 +735,24 @@ public class TesterContext implements Context {
         // NO-OP
     }
 
+    private final Map<String,String> localEncodingMap = new 
ConcurrentHashMap<>();
+
     @Override
     public void addLocaleEncodingMappingParameter(String locale, String 
encoding) {
-        // NO-OP
+        localEncodingMap.put(locale, encoding);
+    }
+    @Override
+    public String getCharset(Locale locale) {
+        // Match full language_country_variant first, then language_country,
+        // then language only
+        String charset = localEncodingMap.get(locale.toString());
+        if (charset == null) {
+            charset = localEncodingMap.get(locale.getLanguage() + "_" + 
locale.getCountry());
+            if (charset == null) {
+                charset = localEncodingMap.get(locale.getLanguage());
+            }
+        }
+        return charset;
     }
 
     @Override
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 42ffdbd..f638141 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -134,6 +134,13 @@
         <code>webapps</code> where it will be deployed (or not) based on the
         current settings for automatic deployment. (markt)
       </add>
+      <fix>
+        <bug>64938</bug>: Align the behaviour when <code>null</code> is passed
+        to the <code>ServletResponse</code> methods
+        <code>setCharacterEncoding()</code>, <code>setContentType()</code> and
+        <code>setLocale()</code> with the recent clarification from the Jakarta
+        Servlet project of the expected behaviour in these cases. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to