Author: markt
Date: Fri Jun 30 20:29:51 2017
New Revision: 1800454

URL: http://svn.apache.org/viewvc?rev=1800454&view=rev
Log:
Improve the Default Servlet's handling of static files when the file encoding 
is not compatible with the required response encoding.

Added:
    tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt   (with props)
Modified:
    tomcat/trunk/build.xml
    tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java
    tomcat/trunk/test/org/apache/catalina/servlets/TestDefaultServlet.java
    tomcat/trunk/webapps/docs/changelog.xml

Modified: tomcat/trunk/build.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/build.xml?rev=1800454&r1=1800453&r2=1800454&view=diff
==============================================================================
--- tomcat/trunk/build.xml (original)
+++ tomcat/trunk/build.xml Fri Jun 30 20:29:51 2017
@@ -560,6 +560,7 @@
         <exclude name="java/org/apache/**/parser/Token*.java" />
         <!-- Exclude simple test files -->
         <exclude name="test/webapp/bug53257/**/*.txt"/>
+        <exclude name="test/webapp/bug49nnn/bug49464-ibm850.txt"/>
         <exclude name="test/webapp-fragments/WEB-INF/classes/*.txt"/>
         <exclude name="test/webresources/**"/>
         <!-- Exclude test files with unusual encodings -->

Modified: tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java?rev=1800454&r1=1800453&r2=1800454&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java 
(original)
+++ tomcat/trunk/java/org/apache/catalina/servlets/DefaultServlet.java Fri Jun 
30 20:29:51 2017
@@ -32,6 +32,8 @@ import java.io.Reader;
 import java.io.Serializable;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.security.AccessController;
 import java.util.ArrayList;
@@ -73,6 +75,7 @@ import org.apache.catalina.connector.Res
 import org.apache.catalina.util.RequestUtil;
 import org.apache.catalina.util.ServerInfo;
 import org.apache.catalina.util.URLEncoder;
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.res.StringManager;
 import org.apache.tomcat.util.security.PrivilegedGetTccl;
 import org.apache.tomcat.util.security.PrivilegedSetTccl;
@@ -230,6 +233,7 @@ public class DefaultServlet extends Http
      * the platform default is used.
      */
     protected String fileEncoding = null;
+    private Charset fileEncodingCharset = null;
 
     /**
      * Minimum size for sendfile usage in bytes.
@@ -287,6 +291,16 @@ public class DefaultServlet extends Http
                 
Integer.parseInt(getServletConfig().getInitParameter("sendfileSize")) * 1024;
 
         fileEncoding = getServletConfig().getInitParameter("fileEncoding");
+        if (fileEncoding == null) {
+            fileEncodingCharset = Charset.defaultCharset();
+            fileEncoding = fileEncodingCharset.name();
+        } else {
+            try {
+                fileEncodingCharset = B2CConverter.getCharset(fileEncoding);
+            } catch (UnsupportedEncodingException e) {
+                throw new ServletException(e);
+            }
+        }
 
         globalXsltFile = getServletConfig().getInitParameter("globalXsltFile");
         contextXsltFile = 
getServletConfig().getInitParameter("contextXsltFile");
@@ -938,7 +952,7 @@ public class DefaultServlet extends Http
             }
         }
 
-        // Check to see if a Filter, Valve of wrapper has written some content.
+        // Check to see if a Filter, Valve or wrapper has written some content.
         // If it has, disable range requests and setting of a content length
         // since neither can be done reliably.
         ServletResponse r = response;
@@ -1000,19 +1014,38 @@ public class DefaultServlet extends Http
                         renderResult = render(getPathPrefix(request), 
resource, encoding);
                     } else {
                         // Output is content of resource
-                        if (!checkSendfile(request, response, resource,
-                                contentLength, null)) {
-                            // sendfile not possible so check if resource
-                            // content is available directly
+                        // Check to see if conversion is required
+                        String outputEncoding = 
response.getCharacterEncoding();
+                        Charset charset = 
B2CConverter.getCharset(outputEncoding);
+                        if (charset.equals(fileEncodingCharset)) {
+                            if (!checkSendfile(request, response, resource,
+                                    contentLength, null)) {
+                                // sendfile not possible so check if resource
+                                // content is available directly
+                                byte[] resourceBody = resource.getContent();
+                                if (resourceBody == null) {
+                                    // Resource content not available, use
+                                    // inputstream
+                                    renderResult = resource.getInputStream();
+                                } else {
+                                    // Use the resource content directly
+                                    ostream.write(resourceBody);
+                                }
+                            }
+                        } else {
+                            // A conversion is required from fileEncoding to
+                            // response encoding
                             byte[] resourceBody = resource.getContent();
+                            InputStream source;
                             if (resourceBody == null) {
-                                // Resource content not available, use
-                                // inputstream
-                                renderResult = resource.getInputStream();
+                                source = resource.getInputStream();
                             } else {
-                                // Use the resource content directly
-                                ostream.write(resourceBody);
+                                source = new 
ByteArrayInputStream(resourceBody);
                             }
+                            OutputStreamWriter osw = new 
OutputStreamWriter(ostream, charset);
+                            PrintWriter pw = new PrintWriter(osw);
+                            copy(resource, source, pw, fileEncoding);
+                            pw.flush();
                         }
                     }
                     // If a stream was configured, it needs to be copied to

Modified: tomcat/trunk/test/org/apache/catalina/servlets/TestDefaultServlet.java
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/catalina/servlets/TestDefaultServlet.java?rev=1800454&r1=1800453&r2=1800454&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/catalina/servlets/TestDefaultServlet.java 
(original)
+++ tomcat/trunk/test/org/apache/catalina/servlets/TestDefaultServlet.java Fri 
Jun 30 20:29:51 2017
@@ -20,7 +20,9 @@ import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
+import java.io.StringReader;
 import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
 import java.util.Date;
 import java.util.HashMap;
@@ -29,6 +31,10 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.TimeZone;
 
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import static org.junit.Assert.assertEquals;
@@ -46,7 +52,9 @@ import org.apache.catalina.Wrapper;
 import org.apache.catalina.startup.SimpleHttpClient;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.http.parser.MediaType;
 import org.apache.tomcat.websocket.server.WsContextListener;
 
 public class TestDefaultServlet extends TomcatBaseTest {
@@ -622,4 +630,136 @@ public class TestDefaultServlet extends
             return true;
         }
     }
+
+    @Test
+    public void testEncodingIncludeStreamOutIso88591() throws Exception {
+        doTestEncoding(false, "ISO-8859-1");
+    }
+
+    @Test
+    public void testEncodingIncludeWriterOutIso88591() throws Exception {
+        doTestEncoding(true, "ISO-8859-1");
+    }
+
+    @Test
+    public void testEncodingIncludeStreamOutUtf8() throws Exception {
+        doTestEncoding(false, "UTF-8");
+    }
+
+    @Test
+    public void testEncodingIncludeWriterOutUtf8() throws Exception {
+        doTestEncoding(true, "UTF-8");
+    }
+
+    @Test
+    public void testEncodingIncludeStreamOutIbm850() throws Exception {
+        doTestEncoding(false, "IBM850");
+    }
+
+    @Test
+    public void testEncodingIncludeWriterOutIbm850() throws Exception {
+        doTestEncoding(false, "IBM850");
+    }
+
+    public void doTestEncoding(boolean useWriter, String outputEncoding) 
throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp");
+
+        Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+
+        Wrapper defaultServlet = Tomcat.addServlet(ctxt, "default", 
DefaultServlet.class.getName());
+        defaultServlet.addInitParameter("fileEncoding", "IBM850");
+        ctxt.addServletMappingDecoded("/", "default");
+
+        Tomcat.addServlet(ctxt, "encoding",
+                new EncodingServlet(outputEncoding, 
"/bug49nnn/bug49464-ibm850.txt", useWriter));
+        ctxt.addServletMappingDecoded("/test", "encoding");
+
+        tomcat.start();
+
+        final ByteChunk res = new ByteChunk();
+        Map<String,List<String>> headers = new HashMap<>();
+
+        int rc = getUrl("http://localhost:"; + getPort() + "/test", res, 
headers);
+
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+        List<String> values = headers.get("Content-Type");
+        if (values.size() == 1) {
+            MediaType mediaType = MediaType.parseMediaType(new 
StringReader(values.get(0)));
+            String charset = mediaType.getCharset();
+            if (charset == null) {
+                res.setCharset(StandardCharsets.ISO_8859_1);
+            } else {
+                res.setCharset(B2CConverter.getCharset(charset));
+            }
+        } else {
+            res.setCharset(StandardCharsets.ISO_8859_1);
+        }
+        Assert.assertEquals("½", res.toString());
+    }
+
+    @Test
+    public void testEncodingDirect() throws Exception {
+        Tomcat tomcat = getTomcatInstance();
+
+        File appDir = new File("test/webapp");
+
+        Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
+
+        Wrapper defaultServlet = Tomcat.addServlet(ctxt, "default", 
DefaultServlet.class.getName());
+        defaultServlet.addInitParameter("fileEncoding", "IBM850");
+        ctxt.addServletMappingDecoded("/", "default");
+
+        tomcat.start();
+
+        final ByteChunk res = new ByteChunk();
+        Map<String,List<String>> headers = new HashMap<>();
+
+        int rc = getUrl("http://localhost:"; + getPort() + 
"/bug49nnn/bug49464-ibm850.txt",
+                res, headers);
+
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+        List<String> values = headers.get("Content-Type");
+        if (values != null && values.size() == 1) {
+            MediaType mediaType = MediaType.parseMediaType(new 
StringReader(values.get(0)));
+            String charset = mediaType.getCharset();
+            if (charset == null) {
+                res.setCharset(StandardCharsets.ISO_8859_1);
+            } else {
+                res.setCharset(B2CConverter.getCharset(charset));
+            }
+        } else {
+            res.setCharset(StandardCharsets.ISO_8859_1);
+        }
+        Assert.assertEquals("½", res.toString());
+    }
+
+    private static class EncodingServlet extends HttpServlet {
+
+        private static final long serialVersionUID = 1L;
+
+        private final String outputEncoding;
+        private final String includeTarget;
+        private final boolean useWriter;
+
+        public EncodingServlet(String outputEncoding, String includeTarget, 
boolean useWriter) {
+            this.outputEncoding = outputEncoding;
+            this.includeTarget = includeTarget;
+            this.useWriter = useWriter;
+        }
+
+        @Override
+        protected void doGet(HttpServletRequest req, HttpServletResponse resp)
+                throws ServletException, IOException {
+            resp.setContentType("text/plain");
+            resp.setCharacterEncoding(outputEncoding);
+            if (useWriter) {
+                resp.getWriter();
+            }
+            resp.flushBuffer();
+            RequestDispatcher rd = req.getRequestDispatcher(includeTarget);
+            rd.include(req, resp);
+        }
+    }
 }

Added: tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt?rev=1800454&view=auto
==============================================================================
--- tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt (added)
+++ tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt [IBM850] Fri Jun 30 
20:29:51 2017
@@ -0,0 +1 @@
+½
\ No newline at end of file

Propchange: tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: tomcat/trunk/test/webapp/bug49nnn/bug49464-ibm850.txt
------------------------------------------------------------------------------
    svn:mime-type = text/plain; charset="IBM850"

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: 
http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1800454&r1=1800453&r2=1800454&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Fri Jun 30 20:29:51 2017
@@ -48,6 +48,11 @@
   <subsection name="Catalina">
     <changelog>
       <fix>
+        <bug>49464</bug>: Improve the Default Servlet's handling of static 
files
+        when the file encoding is not compatible with the required response
+        encoding. (markt)
+      </fix>
+      <fix>
         <bug>61214</bug>: Remove deleted attribute <code>servlets</code> from
         the Context MBean description. Patch provided by Alexis Hassler. 
(markt)
       </fix>



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

Reply via email to