Browse Source

Use proper UTF-8 encoding for Content-Disposition filenames (#19527) (#6607)

tags/7.7.7
Leif Åstrand 7 years ago
parent
commit
94df5a7ed3

+ 6
- 9
server/src/main/java/com/vaadin/server/DownloadStream.java View File

import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;


import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;


import com.vaadin.util.EncodeUtil;

/** /**
* Downloadable stream. * Downloadable stream.
* <p> * <p>
* @return A value for inclusion in a Content-Disposition header * @return A value for inclusion in a Content-Disposition header
*/ */
public static String getContentDispositionFilename(String filename) { 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);
} }


/** /**

+ 71
- 0
server/src/main/java/com/vaadin/util/EncodeUtil.java View File

/*
* 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);
}
}

}

+ 10
- 6
server/src/test/java/com/vaadin/server/DownloadStreamTest.java View File



import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URLEncoder;


import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;


public class DownloadStreamTest { 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; private DownloadStream stream;


@Before @Before


stream.writeResponse(mock(VaadinRequest.class), response); stream.writeResponse(mock(VaadinRequest.class), response);


String encodedFileName = URLEncoder.encode(filename, "utf-8");
verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION), verify(response).setHeader(eq(DownloadStream.CONTENT_DISPOSITION),
contains(String.format("filename=\"%s\";", encodedFileName))); 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)));
} }
} }

+ 7
- 6
server/src/test/java/com/vaadin/tests/server/ClassesSerializableTest.java View File



private static String[] BASE_PACKAGES = { "com.vaadin" }; 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\\.external\\.org\\.apache\\.commons\\.fileupload\\..*", //
"com\\.vaadin\\.launcher\\..*", // "com\\.vaadin\\.launcher\\..*", //
"com\\.vaadin\\.client\\..*", // "com\\.vaadin\\.client\\..*", //
// interfaces // interfaces
"com\\.vaadin\\.server\\.LegacyCommunicationManager.*", // "com\\.vaadin\\.server\\.LegacyCommunicationManager.*", //
"com\\.vaadin\\.buildhelpers.*", // "com\\.vaadin\\.buildhelpers.*", //
"com\\.vaadin\\.util\\.EncodeUtil.*", //
"com\\.vaadin\\.util\\.ReflectTools.*", // "com\\.vaadin\\.util\\.ReflectTools.*", //
"com\\.vaadin\\.data\\.util\\.ReflectTools.*", // "com\\.vaadin\\.data\\.util\\.ReflectTools.*", //
"com\\.vaadin\\.data\\.util.BeanItemContainerGenerator.*", "com\\.vaadin\\.data\\.util.BeanItemContainerGenerator.*",
nonSerializableString += ")"; 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);
} }
} }


while (e.hasMoreElements()) { while (e.hasMoreElements()) {
JarEntry entry = e.nextElement(); JarEntry entry = e.nextElement();
if (entry.getName().endsWith(".class")) { if (entry.getName().endsWith(".class")) {
String nameWithoutExtension = entry.getName()
.replaceAll("\\.class", "");
String nameWithoutExtension = entry.getName().replaceAll(
"\\.class", "");
String className = nameWithoutExtension.replace('/', '.'); String className = nameWithoutExtension.replace('/', '.');
classes.add(className); classes.add(className);
} }

+ 32
- 0
server/src/test/java/com/vaadin/util/EncodeUtilTest.java View File

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

+ 2
- 2
uitest/src/main/java/com/vaadin/tests/components/FileDownloaderUI.java View File

addComponents("Class resource pdf", resource, components); addComponents("Class resource pdf", resource, components);


Button downloadUtf8File = new Button("Download UTF-8 named file"); 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.setOverrideContentType(false);
fd.extend(downloadUtf8File); fd.extend(downloadUtf8File);
addComponent(downloadUtf8File); addComponent(downloadUtf8File);

uitest/src/main/resources/com/vaadin/tests/components/embedded/åäö-日本語.pdf → uitest/src/main/resources/com/vaadin/tests/components/embedded/File åäö-日本語.pdf View File


Loading…
Cancel
Save