]> source.dussan.org Git - vaadin-framework.git/commitdiff
Use proper UTF-8 encoding for Content-Disposition filenames (#19527) (#6607)
authorLeif Åstrand <legioth@gmail.com>
Tue, 13 Dec 2016 09:28:55 +0000 (11:28 +0200)
committerPekka Hyvönen <pekka@vaadin.com>
Tue, 13 Dec 2016 09:28:55 +0000 (11:28 +0200)
server/src/main/java/com/vaadin/server/DownloadStream.java
server/src/main/java/com/vaadin/util/EncodeUtil.java [new file with mode: 0644]
server/src/test/java/com/vaadin/server/DownloadStreamTest.java
server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java
server/src/test/java/com/vaadin/util/EncodeUtilTest.java [new file with mode: 0644]
uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java
uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf [new file with mode: 0644]
uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf [deleted file]

index fdc757bd5e9ec21162658db87d59ba9696bc99e2..c13cad0018cc19fa55b1b2e9f0fac0fee61b99bd 100644 (file)
@@ -20,14 +20,14 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
 import javax.servlet.http.HttpServletResponse;
 
+import com.vaadin.util.EncodeUtil;
+
 /**
  * Downloadable stream.
  * <p>
@@ -329,13 +329,10 @@ public class DownloadStream implements Serializable {
      * @return A value for inclusion in a Content-Disposition header
      */
     public static String getContentDispositionFilename(String filename) {
-        try {
-            String encodedFilename = URLEncoder.encode(filename, "UTF-8");
-            return String.format("filename=\"%s\"; filename*=utf-8''%s",
-                    encodedFilename, encodedFilename);
-        } catch (UnsupportedEncodingException e) {
-            return null;
-        }
+        String encodedFilename = EncodeUtil.rfc5987Encode(filename);
+
+        return String.format("filename=\"%s\"; filename*=utf-8''%s",
+                encodedFilename, encodedFilename);
     }
 
     /**
diff --git a/server/src/main/java/com/vaadin/util/EncodeUtil.java b/server/src/main/java/com/vaadin/util/EncodeUtil.java
new file mode 100644 (file)
index 0000000..3319211
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.util;
+
+import java.nio.charset.Charset;
+
+/**
+ * Utilities related to various encoding schemes.
+ *
+ * @author Vaadin Ltd
+ * @since
+ */
+public class EncodeUtil {
+    private static final Charset utf8 = Charset.forName("UTF-8");
+
+    private EncodeUtil() {
+        // Static utils only
+    }
+
+    /**
+     * Encodes the given string to UTF-8 <code>value-chars</code> as defined in
+     * RFC5987 for use in e.g. the <code>Content-Disposition</code> HTTP header.
+     *
+     * @param value
+     *            the string to encode, not <code>null</code>
+     * @return the encoded string
+     */
+    public static String rfc5987Encode(String value) {
+        StringBuilder builder = new StringBuilder();
+
+        for (int i = 0; i < value.length();) {
+            int cp = value.codePointAt(i);
+            if (cp < 127 && (Character.isLetterOrDigit(cp) || cp == '.')) {
+                builder.append((char) cp);
+            } else {
+                // Create string from a single code point
+                String cpAsString = new String(new int[] { cp }, 0, 1);
+
+                appendHexBytes(builder, cpAsString.getBytes(utf8));
+            }
+
+            // Advance to the next code point
+            i += Character.charCount(cp);
+        }
+
+        return builder.toString();
+    }
+
+    private static void appendHexBytes(StringBuilder builder, byte[] bytes) {
+        for (byte byteValue : bytes) {
+            // mask with 0xFF to compensate for "negative" values
+            int intValue = byteValue & 0xFF;
+            String hexCode = Integer.toString(intValue, 16);
+            builder.append('%').append(hexCode);
+        }
+    }
+
+}
index f302163ef7f191217017dbb48e505d66cac99dbf..79347a3f3d1f06df89c70b6129731b6cc8a4dd67 100644 (file)
@@ -7,13 +7,16 @@ import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URLEncoder;
 
 import org.junit.Before;
 import org.junit.Test;
 
 public class DownloadStreamTest {
-    private String filename = "日本語.png";
+    private String filename = "A å日.png";
+    private String encodedFileName = "A" + "%20" // space
+            + "%c3%a5" // å
+            + "%e6%97%a5" // 日
+            + ".png";
     private DownloadStream stream;
 
     @Before
@@ -27,11 +30,12 @@ public class DownloadStreamTest {
 
         stream.writeResponse(mock(VaadinRequest.class), response);
 
-        String encodedFileName = URLEncoder.encode(filename, "utf-8");
         verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION),
                 contains(String.format("filename=\"%s\";", encodedFileName)));
-        verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION),
-                contains(
-                        String.format("filename*=utf-8''%s", encodedFileName)));
+        verify(response)
+                .setHeader(
+                        eq(DownloadStream.CONTENT_DISPOSITION),
+                        contains(String.format("filename*=utf-8''%s",
+                                encodedFileName)));
     }
 }
index 28bf497afe117db969f507a3d4581091c31a86fb..f32af8a3bcda57cf361a0a7cb2d07064857be383 100644 (file)
@@ -27,7 +27,8 @@ public class ClassesSerializableTest {
 
     private static String[] BASE_PACKAGES = { "com.vaadin" };
 
-    private static String[] EXCLUDED_PATTERNS = { "com\\.vaadin\\.demo\\..*", //
+    private static String[] EXCLUDED_PATTERNS = {
+            "com\\.vaadin\\.demo\\..*", //
             "com\\.vaadin\\.external\\.org\\.apache\\.commons\\.fileupload\\..*", //
             "com\\.vaadin\\.launcher\\..*", //
             "com\\.vaadin\\.client\\..*", //
@@ -58,6 +59,7 @@ public class ClassesSerializableTest {
             // interfaces
             "com\\.vaadin\\.server\\.LegacyCommunicationManager.*", //
             "com\\.vaadin\\.buildhelpers.*", //
+            "com\\.vaadin\\.util\\.EncodeUtil.*", //
             "com\\.vaadin\\.util\\.ReflectTools.*", //
             "com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
             "com\\.vaadin\\.data\\.util.BeanItemContainerGenerator.*",
@@ -148,9 +150,8 @@ public class ClassesSerializableTest {
                     nonSerializableString += ")";
                 }
             }
-            Assert.fail(
-                    "Serializable not implemented by the following classes and interfaces: "
-                            + nonSerializableString);
+            Assert.fail("Serializable not implemented by the following classes and interfaces: "
+                    + nonSerializableString);
         }
     }
 
@@ -272,8 +273,8 @@ public class ClassesSerializableTest {
         while (e.hasMoreElements()) {
             JarEntry entry = e.nextElement();
             if (entry.getName().endsWith(".class")) {
-                String nameWithoutExtension = entry.getName()
-                        .replaceAll("\\.class", "");
+                String nameWithoutExtension = entry.getName().replaceAll(
+                        "\\.class", "");
                 String className = nameWithoutExtension.replace('/', '.');
                 classes.add(className);
             }
diff --git a/server/src/test/java/com/vaadin/util/EncodeUtilTest.java b/server/src/test/java/com/vaadin/util/EncodeUtilTest.java
new file mode 100644 (file)
index 0000000..7bac539
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.util;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class EncodeUtilTest {
+    @Test
+    public void rfc5987Encode() {
+        Assert.assertEquals("A", EncodeUtil.rfc5987Encode("A"));
+        Assert.assertEquals("%20", EncodeUtil.rfc5987Encode(" "));
+        Assert.assertEquals("%c3%a5", EncodeUtil.rfc5987Encode("å"));
+        Assert.assertEquals("%e6%97%a5", EncodeUtil.rfc5987Encode("日"));
+
+        Assert.assertEquals("A" + "%20" + "%c3%a5" + "%e6%97%a5",
+                EncodeUtil.rfc5987Encode("A å日"));
+    }
+}
index 29c8ab5eaa1579198d3d441157d18054e3f180e1..e4183cd40bfc07702e8bf2ae2895a0fade753589 100644 (file)
@@ -99,8 +99,8 @@ public class FileDownloaderUI extends AbstractTestUIWithLog {
         addComponents("Class resource pdf", resource, components);
 
         Button downloadUtf8File = new Button("Download UTF-8 named file");
-        FileDownloader fd = new FileDownloader(
-                new ClassResource(new EmbeddedPdf().getClass(), "åäö-日本語.pdf"));
+        FileDownloader fd = new FileDownloader(new ClassResource(
+                new EmbeddedPdf().getClass(), "File åäö-日本語.pdf"));
         fd.setOverrideContentType(false);
         fd.extend(downloadUtf8File);
         addComponent(downloadUtf8File);
diff --git a/uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf b/uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf
new file mode 100644 (file)
index 0000000..e44a87e
Binary files /dev/null and "b/uitest/src/main/resources/com/vaadin/tests/components/embedded/File \303\245\303\244\303\266-\346\227\245\346\234\254\350\252\236.pdf" differ
diff --git a/uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf b/uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf
deleted file mode 100644 (file)
index e44a87e..0000000
Binary files "a/uitest/src/main/resources/com/vaadin/tests/components/embedded/\303\245\303\244\303\266-\346\227\245\346\234\254\350\252\236.pdf" and /dev/null differ