@@ -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); | |||
} | |||
/** |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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))); | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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 å日")); | |||
} | |||
} |
@@ -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); |