Skip to content

Commit

Permalink
Merge pull request #4 from Widen/fix/DAM-8293-utf8-attachment-filenames
Browse files Browse the repository at this point in the history
fix: add proper rfc5987 UTF-8 attachment filenames
  • Loading branch information
sagebind authored Jan 4, 2017
2 parents 1c58801 + 46a746b commit 359bc84
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 50 deletions.
9 changes: 2 additions & 7 deletions src/com/widen/util/CloudfrontUrlBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,9 @@ public String toString()
builder.addParameters(parameters);
builder.modeFullyQualified();

if (attachmentFilename != null)
if (StringUtilsInternal.isNotBlank(attachmentFilename))
{
String cleanedFilename = InternalUtils.cleanAttachmentFilename(attachmentFilename);

if (StringUtilsInternal.isNotBlank(cleanedFilename))
{
builder.addParameter("response-content-disposition", String.format("attachment; filename=\"%s\"", cleanedFilename));
}
builder.addParameter("response-content-disposition", HttpUtils.createContentDispositionHeader("attachment", attachmentFilename));
}

String cannedPolicy = String.format("{\"Statement\":[{\"Resource\":\"%s\",\"Condition\":{\"DateLessThan\":{\"AWS:EpochTime\":%s}}}]}", builder.toString(), expireDate.getExpiresUtcSeconds());
Expand Down
37 changes: 37 additions & 0 deletions src/com/widen/util/HttpUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.widen.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.Normalizer;

public class HttpUtils
{
public static String createContentDispositionHeader(String type, String filename)
{
// Most browsers don't normally support UTF-8 in HTTP headers (HTTP officially supports only ISO-8859-1). In
// practice, S3 only supports ASCII characters, so strip everything from the filename out not in the ASCII range.
String asciiFilename = Normalizer
.normalize(filename, Normalizer.Form.NFD)
.replaceAll("[^\\x20-\\x7E]", "");

// Create the base header value.
String header = String.format("%s; filename=\"%s\"", type, asciiFilename);

// For clients that support RFC 5987, we can URL encode the UTF-8 encoded filename and pass it in as an
// additional parameter. If the ASCII filename differs from the given filename, add the additional parameter.
if (!asciiFilename.equals(filename))
{
try
{
String utf8EncodedFilename = URLEncoder.encode(filename, "UTF-8").replaceAll("\\+", "%20");
header += String.format("; filename*=UTF-8''%s", utf8EncodedFilename);
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e);
}
}

return header;
}
}
15 changes: 1 addition & 14 deletions src/com/widen/util/InternalUtils.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package com.widen.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.Normalizer;
import java.io.*;

public class InternalUtils
{
Expand Down Expand Up @@ -48,13 +44,4 @@ public static long copy(InputStream input, OutputStream output) throws IOExcepti
return count;
}

static String cleanAttachmentFilename(String filename)
{
// Most browsers don't support UTF-8 in HTTP headers (HTTP officially supports only ISO-8859-1). In practice, S3
// only supports ASCII characters, so strip everything from the filename out not in the ASCII range.
return Normalizer
.normalize(filename, Normalizer.Form.NFD)
.replaceAll("[^\\x20-\\x7E]", "");
}

}
9 changes: 1 addition & 8 deletions src/com/widen/util/S3UrlBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
*/
package com.widen.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.util.Date;
import java.util.HashMap;
Expand Down Expand Up @@ -327,12 +325,7 @@ else if (BucketEncoding.VIRTUAL_DNS.equals(requestedBucketEncoding))
{
canSign();

String cleanedFilename = InternalUtils.cleanAttachmentFilename(attachmentFilename);

if (StringUtilsInternal.isNotBlank(cleanedFilename))
{
builder.addParameter("response-content-disposition", String.format("attachment; filename=\"%s\"", cleanedFilename));
}
builder.addParameter("response-content-disposition", HttpUtils.createContentDispositionHeader("attachment", attachmentFilename));
}

if (expireDate.isSet())
Expand Down
2 changes: 1 addition & 1 deletion test/com/widen/util/CloudfrontUrlBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void testNonAsciiCharsInAttachment()
{
CloudfrontUrlBuilder builder = new CloudfrontUrlBuilder("dnnfhn216qiqy.cloudfront.net", "/0/b/c/d/test.jpeg", "APKAIW7O5EPF5UBMJ7KQ", pem).withAttachmentFilename("+ƒoo.jpg").expireAt(new Date(1381356586000L));

Assert.assertEquals("http://dnnfhn216qiqy.cloudfront.net/0/b/c/d/test.jpeg?response-content-disposition=attachment%3B%20filename%3D%22%2Boo.jpg%22&Expires=1381356586&Signature=RCox6ROYo4adkdPkG-6HFIoAcyQLh~aih4wt4yfplKVzQBFXN2hUQfTN17w6~IayG9sYKKA11iNEWFJRGXZzlE32TUDFNW4x6ETir9mlPsq5EKeKzRpZQluQhdW1TSfDyvPQNnMdObCt3MZCpl~BJKfG6FJtyFWot2~ISGU-URQBq0ItnLzBkigmM4a2xKI2NQ0W-bIEqowfigBvaf-GBtNrknXt7sRyc6mE1XGxh4zh-9t1TJDRH3EvzzJWpATUJrh8D69kuv7BoJ~jKWMrbXFSXHDQjP1ZFAPAY9fSjaoyrPh1AlYA1s6qruGPcr7JmiVoUiAakDzQmq92xjdGnA__&Key-Pair-Id=APKAIW7O5EPF5UBMJ7KQ", builder.toString());
Assert.assertEquals("http://dnnfhn216qiqy.cloudfront.net/0/b/c/d/test.jpeg?response-content-disposition=attachment%3B%20filename%3D%22%2Boo.jpg%22%3B%20filename*%3DUTF-8%27%27%252B%25C6%2592oo.jpg&Expires=1381356586&Signature=h8Z0hTcpvPzSxmgMjQGynOSCN-2pFTVnJQPG8bxXQ6rDWvVnVPvMOt3OrkACtLFf7NAhJbx4XpJTo3shlRYsG4E2cS5aRB6ko2N0C18hq3scySjZzLAMVLpqOTR6rK9j4Rc9dHpuZ6IlZ~qJ2xE8C516JvRXY4TLZp84WjBQZQOe6FiLuVy-sIFfAs5X1eqWgHCJKLgqBeozJlijH8jv3V1kTJADoGvOpvvKXDSjujv~u5QJ1pE6COo6vHn4PKNf4Dh-RiWU--Uqbtw26qo8fwQmBo6V4TJeQXwzWaZl74hwr7x4bUArdZLYQz892d3aHzdtZucKgIl~xMQy6kchVw__&Key-Pair-Id=APKAIW7O5EPF5UBMJ7KQ", builder.toString());
}

}
33 changes: 33 additions & 0 deletions test/com/widen/util/HttpUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.widen.util;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class HttpUtilsTest
{
@Test
public void testCreateContentDispositionHeader()
{
assertEquals(
"inline; filename=\"foo.jpg\"",
HttpUtils.createContentDispositionHeader("inline", "foo.jpg")
);
assertEquals(
"attachment; filename=\"+oo.jpg\"; filename*=UTF-8''%2B%C6%92oo.jpg",
HttpUtils.createContentDispositionHeader("attachment", "+ƒoo.jpg")
);
assertEquals(
"attachment; filename=\"foo.jpg\"; filename*=UTF-8''%F0%A2%83%87%F0%A2%9E%B5%F0%A2%AB%95foo%F0%A2%AD%83%F0%A2%AF%8A%F0%A2%B1%91%F0%A2%B1%95.jpg",
HttpUtils.createContentDispositionHeader("attachment", "𢃇𢞵𢫕foo𢭃𢯊𢱑𢱕.jpg")
);
assertEquals(
"attachment; filename=\"helloworld.jpg\"; filename*=UTF-8''hello%0Aworld.jpg",
HttpUtils.createContentDispositionHeader("attachment", "hello\nworld.jpg")
);
assertEquals(
"attachment; filename=\"resume.pdf\"; filename*=UTF-8''r%C3%A9sum%C3%A9.pdf",
HttpUtils.createContentDispositionHeader("attachment", "résumé.pdf")
);
}
}
18 changes: 0 additions & 18 deletions test/com/widen/util/InternalUtilsTest.java

This file was deleted.

4 changes: 2 additions & 2 deletions test/com/widen/util/S3UrlBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class S3UrlBuilderTest

private static final String awsAccount = "AKIAJKECYSQBZYJDUDSQ";

private static final String awsPrivateKey = System.getProperty("awsPrivateKey");
private static final String awsPrivateKey = "System.getProperty(\"awsPrivateKey\")";

static
{
Expand Down Expand Up @@ -135,7 +135,7 @@ public void testNonAsciiCharsInAttachment()
{
S3UrlBuilder builder = new S3UrlBuilder("urlbuildertests.widen.com", "foo.jpeg").withAttachmentFilename("+ƒoo.jpeg").expireAt(farFuture).usingCredentials(awsAccount, awsPrivateKey);

assertEquals("http://urlbuildertests.widen.com.s3.amazonaws.com/foo.jpeg?response-content-disposition=attachment%3B%20filename%3D%22%2Boo.jpeg%22&Signature=eBNNBRl7DlXOjhIb5YSlpR9BPOg%3D&AWSAccessKeyId=AKIAJKECYSQBZYJDUDSQ&Expires=1522540800", builder.toString());
assertEquals("http://urlbuildertests.widen.com.s3.amazonaws.com/foo.jpeg?response-content-disposition=attachment%3B%20filename%3D%22%2Boo.jpeg%22%3B%20filename*%3DUTF-8%27%27%252B%25C6%2592oo.jpeg&Signature=eBNNBRl7DlXOjhIb5YSlpR9BPOg%3D&AWSAccessKeyId=AKIAJKECYSQBZYJDUDSQ&Expires=1522540800", builder.toString());
}

@Test
Expand Down

0 comments on commit 359bc84

Please sign in to comment.