Search code examples
c#.netamazon-web-servicesamazon-s3amazon-cloudfront

Cloudfront Signed URLs not working with S3 content disposition filenames with spaces using .NET SDK


I have setup Cloudfront Signed URLs with an S3 origin correctly and am using the response-content-disposition query string parameter to specify the file download name. The signed URLs I generate using the .NET AWS SDK AmazonCloudFrontUrlSigner.GetCannedSignedURL method work correctly when the content disposition filename doesn't contain spaces. However, if the filename contains spaces, I get access denied. So, something like the code below will generate a URL that gives access denied.

var contentDisposition = HttpUtility.UrlEncode("attachment;filename=My File.txt");
var key = "example.txt?response-content-disposition="+contentDisposition;
return AmazonCloudFrontUrlSigner.GetCannedSignedURL(
    AmazonCloudFrontUrlSigner.Protocol.https,
    "myBucket",
    cloudFrontPrivateKey,
    key, cloudFrontAccessKeyId, expirationDateTime);

It clearly seems to have something to do with the URL encoding.

I have read through all the information in the docs about Serving Private Content through CloudFront. I read the code of the AmazonCloudFrontUrlSigner class. I've also tried an number of combinations of UrlEncode like not encoding, encoding only the filename portion and even not encoding but replacing with the encoded version after the signed URL is generated. All of those either give access denied or an error that the signature doesn't match the url.


Solution

  • The HttpUtility.UrlEncode method encodes spaces as + which is acceptable according to the standards. However, for some reason I don't understand, this causes problems with the Signed URLs and content disposition. The other encoding of space as %20 works correctly. So after encoding, replace the + with %20. The working version is:

    var contentDisposition = HttpUtility.UrlEncode("attachment;filename=My File.txt");
    contentDisposition = contentDisposition.Replace("+", "%20");
    var key = "example.txt?response-content-disposition="+contentDisposition;
    return AmazonCloudFrontUrlSigner.GetCannedSignedURL(
        AmazonCloudFrontUrlSigner.Protocol.https,
        "myBucket",
        cloudFrontPrivateKey,
        key, cloudFrontAccessKeyId, expirationDateTime);