aws-sdk-js icon indicating copy to clipboard operation
aws-sdk-js copied to clipboard

Cloudfront Signer not works with UTF-8 filenames in content-disposition

Open rysi3k opened this issue 6 years ago • 7 comments

Confirm by changing [ ] to [x] below to ensure that it's a bug:

Describe the bug I want to sign url by Cloudfront.Signer but filename could contain special or diacritic characters. On S3 file is stored on random name, but I want to download it in proper name so need to use "response-content-dispostion" query parameter. I tried to sign it using this code:

function urlEncode(str) {
        return encodeURIComponent(str)
            .replace(/!/g, '%21')
            .replace(/'/g, '%27')
            .replace(/\(/g, '%28')
            .replace(/\)/g, '%29')
            .replace(/\*/g, '%2A')
            .replace(/~/g, '%7E');
    }
const filename = 'abc def.mp4'; // for simple example, not works for this as well
const s = new AWS.CloudFront.Signer(....);
const signedUrl = s.getSignedUrl({
                expires: ~~(Date.now() / 1000) + expirationSeconds,
                url: `https://my-url.com/folder/file.mp4?response-content-disposition=${this.urlEncode(`attachment;filename*=UTF-8''${urlEncode(filename)}`)}`,
            });

In result, i get signed url with not works - cloudfront return AccessDenied message.

https://my-url.com/folder/file.mp4?response-content-disposition=attachment%3Bfilename*%3DUTF-8''abc%2520def.mp4&Expires=1573653786&Key-Pair-Id=....&Signature=....

There is not encoded * ' (and maybe more?) chars. In PHP version of sdk everything works.

$client = new Aws\CloudFront\CloudFrontClient([
    'region' => 'eu-central-1',
    "version" => "2019-03-26"
]);

$resourceKey = 'https://my-url.com/folder/file.mp4?response-content-disposition='.urlencode('attachment;filename*=UTF-8\'\''.rawurlencode('abc def.mp4'));
$expires = time() + 300;

// Create a signed URL for the resource using the canned policy
echo($client->getSignedUrl([
    'url' => $resourceKey,
    'expires' => $expires,
    'private_key' => 'key.pem',
    'key_pair_id' => '...'
])."\n");

returns:

https://my-url.com/folder/filemp4?response-content-disposition=attachment%3Bfilename%2A%3DUTF-8%27%27abc%2520def.mp4&Expires=1573643427&Signature=...&Key-Pair-Id=...

Is the issue in the browser/Node.js? Node.js

If on Node.js, are you running this on AWS Lambda? No

Details of the browser/Node.js version 12.12.0

SDK version number 2.548.0

To Reproduce (observed behavior) see above

Expected behavior SDK should return proper encoded url.

Screenshots If applicable, add screenshots to help explain your problem.

Additional context Workaroud which works is additional replace (to revert original response-content-disposition) after generating signed url:

const s = new AWS.CloudFront.Signer(...);

        const preparedUrl = `https://my-url.com/folder/file.mp4?response-content-disposition=${this.urlEncode(`attachment;filename*=UTF-8''${urlEncode(filename)}`)}`
        const signedUrl = s.getSignedUrl({
                expires: ~~(Date.now() / 1000) + expirationSeconds,
                url: preparedUrl,
            }).replace(/\?response-content-disposition=(.*?)&/, (matched) => {
                return '?response-content-disposition=' + (preparedUrl.match(/\?response-content-disposition=(.*?)(?:&|$)/) || [matched]).pop() + '&';
            });

rysi3k avatar Nov 13 '19 12:11 rysi3k

Hey @rysi3k, apologies for late reply.

I can look into it, if it is still a persisting issue.

Additionally we would welcome a PR for the fix if it doesn't break anything.

ajredniwja avatar Dec 20 '19 02:12 ajredniwja

Yes, problem still persists. To be honest, i have no good idea (and time right now) how to fix it in good way :)

rysi3k avatar Jan 09 '20 20:01 rysi3k

Hi, any update on this Issue?

alex-sh-li avatar Jul 06 '20 07:07 alex-sh-li

I am seeing this issue as well. Seems pretty straightforward to repro. Here's an example url: https://xxxxxx.com/737d5867-af87-43f6-aada-c430739112cd/Thumbnail?response-content-disposition=inline%3B%20filename%3D%22bernstein-firebird%3F.jpg-Thumbnail.png%22%3B%20filename*%3DUTF-8%27%27bernstein-firebird%25E4%25BC%25A0.jpg-Thumbnail.png&Expires=1611354894&Key-Pair-Id=APKAJRJSTNY6G6ZJ2I4A&Signature=Fe1ki1PV-8To1X6~vPw-4LyZQ8N6W79TDcXsvT6hnihsCp1NttGu~QK2i9BEVA7orHbZXZRjqTGFUzIsL7L8Y01H3qDlDNXh1Jtvy3EbFE5lESgDH22Wer-FrkmlfbyRdZmlSr6Kzd7H~w-luLWwfTfjhCJjqkgELVU~ZhqVXZhU5aK2ISE2EUbCNdpIehdZ1akSgzkIZlyT4KvlUT1xBAGLEK0XBy6IGsccEEBTtTY-dKPnQo7HIA0e71xopDClnZRZUCftekEIz5nMSXyGU5TexKbCldcUXIqfUNPqF-tdUYxVDKnJSJcERF8xo4TcbUxmtdcoipaMVb0G7U54cg__ The actual filename is bernstein-firebird传.jpg-Thumbnail.png. Seems to be encoded correctly in the content-disposition.

bbrown-extensis avatar Jan 23 '21 18:01 bbrown-extensis

Looking at this further, the problem is that when I send a url with response-content-definiton=attachment%3B%20filename%3D%22name%3F.jpg%22%3B%20filename*%3DUTF-8%27%27filename%25CC%2588.jpg to AWS.CloudFront.Signer.getSignedUrl it gives me a url with the UTF-8%27%27 part converted to UTF-8''. In other words it decodes the %27's to single quotes. This url fails on the command line, but works in a browser because browsers surreptitiously encode single quotes. getSignedUrl should preserve the encoding.

bbrown-extensis avatar Jan 26 '21 17:01 bbrown-extensis

Greetings! We’re closing this issue because it has been open a long time and hasn’t been updated in a while and may not be getting the attention it deserves. We encourage you to check if this is still an issue in the latest release and if you find that this is still a problem, please feel free to comment or open a new issue.

github-actions[bot] avatar Oct 07 '22 00:10 github-actions[bot]

Fix this issue. There's a PR fixing it.

realies avatar Oct 07 '22 00:10 realies

Hi Everyone,

Apologies that this was never addressed. v3 SDK has its own Cloudfront signer that handles this encoding correctly.

Example:

import { getSignedUrl } from '@aws-sdk/cloudfront-signer';
import { generateKeyPairSync } from 'crypto';

// Generate a temporary RSA key pair
const { privateKey } = generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: {
    type: 'spki',
    format: 'pem'
  },
  privateKeyEncoding: {
    type: 'pkcs8',
    format: 'pem'
  }
});

const keyPairId = 'KXXXXXXXXXXXXXXXXXXX';

// complex filename:
const baseUrl = `https://example.com/file.mp4?response-content-disposition=attachment;filename*=UTF-8''My File Name$@?.mp4`;
const signedUrl = getSignedUrl({
  url: baseUrl,
  keyPairId,
  privateKey,
  dateLessThan: new Date(Date.now() + 300 * 1000).toISOString()
});

console.log('Signed URL (v3):', signedUrl);

Results in the URL correctly getting encoded (formatted for better readability):

Signed URL (v3):
https://example.com/file.mp4?
response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27My%20File%20Name%24%40%3F.mp4&
Expires=1719865996&
Key-Pair-Id=KXXXXXXXXXXXXXXXXXXX&
Signature=REDACTED

Since v2 is on the road to deprecation, and this specific issue has a workaround in v2, and the issue does not persist to v3 , I'm inclined to close this issue since it is not going to get prioritized.

Thanks again for your engagement on the thread. If there is an issue with the v3 cloudfront signer, please open a new issue on the v3 repo where we are more actively looking at new issues.

Thanks, Ran~

RanVaknin avatar Jul 01 '24 20:07 RanVaknin

@RanVaknin, I made a PR which fixes this a long time ago. Nobody bothered to have a look. v2 is deprecated in a while.

realies avatar Jul 01 '24 22:07 realies