Aws-sdk-js: Problem uploading svg files to S3

Created on 14 Sep 2016  路  4Comments  路  Source: aws/aws-sdk-js

I am following the guide here to try and upload files to S3.

  • The user selects a file to upload via a html file input
  • the client requests a signedUrl for putObject from the node server
  • the client PUTs the file on to S3 using the signedUrl

This is successfully working for various filetypes (tested with .png, .jpg, .gif and .txt) but fails whenever trying to upload an svg file (ContentType: image/svg+xml).

The PUT request responds with _403 Forbidden_, with statusText _Error Forbidden_.

An example signedUrl is:

https://BUCKET.s3-us-west-2.amazonaws.com/circle-with-header.svg?AWSAccessKeyId=ID&Content-Type=image%2Fsvg%20xml&Expires=1473857180&Signature=WTV9Ex%2F8nYh10791Z0c3j93R4z0%3D&x-amz-acl=public-read

Notice that the ContentType is encoded as: Content-Type=image%2Fsvg%20xml (where %20 is a space, rather than a +)

It seems that it is encoding image/svg+xml as image/svg xml

I have tried manually replacing this with %2B instead but still receive the same error.

My node code is:

router.get('/sign-s3', (req, res, next) => {
  const s3 = new AWS.S3();
  const fileName = req.query['file-name'];
  const fileType = req.query['file-type'];
  const s3Params = {
    Bucket: S3_BUCKET,
    Key: fileName,
    Expires: 60,
    ContentType: fileType,
    ACL: 'public-read'
  };

  s3.getSignedUrl('putObject', s3Params, (err, data) => {
    if (err) {
      console.log(err);
      return res.end();
    }
    const returnData = {
      signedRequest: data,
      url: `https://${S3_BUCKET}.s3.amazonaws.com/${fileName}`
    };
    res.write(JSON.stringify(returnData));
    res.end();
  });
});

The client-side uploadFile operation is:

function uploadFile(file, signedRequest, url){
  const xhr = new XMLHttpRequest();
  // signedRequest = signedRequest.replace('svg%20', 'svg%2B')
  xhr.open('PUT', signedRequest);
  xhr.onreadystatechange = () => {
    if(xhr.readyState === 4){
      if(xhr.status === 200){
        console.log('new url');
        console.log(url);
      }
      else{
        console.log('Could not upload file.');
        console.log("Error", xhr.statusText);
      }
    }
  };
  xhr.send(file);
}

Is uploading SVGs supported?

There is a post here of a maybe similar issue happening with the dot-net sdk: http://stackoverflow.com/questions/9051650/why-is-my-s3-pre-signed-request-invalid-when-i-set-a-response-header-override-th

As a side-note, I have noticed the official docs say that ContentType does not have to be provided as a param to the getSignedUrl operation, but that is not working for me in any case (for pngs, gifs, or jpgs for example). Is that example valid?

var params = {Bucket: 'myBucket', Key: 'myKey'};
var url = s3.getSignedUrl('putObject', params);
console.log("The URL is", url);

SDK version: 2.6.2
Node version: 6.5.0

guidance

Most helpful comment

@bhishp
Can you log what your s3Params looks like, minus the bucket name, when you are trying to upload an svg?

I recreated your sample (though just using http instead of express), and was able to create a valid url and upload with it.

// s3Params
{ Bucket: 'BUCKET',
  Key: 'test.svg',
  Expires: 60,
  ContentType: 'image/svg+xml',
  ACL: 'public-read' }

URL:

https://BUCKET.s3-us-west-2.amazonaws.com/test.svg?AWSAccessKeyId=ID&Content-Type=image%2Fsvg%2Bxml&Expires=1473874113&Signature=l57f1QQzt7Ilq%2BnYR%2F%2BsurQNRZg%3D&x-amz-acl=public-read

In my url, the + is encoded as %2B, not a space.

Can you also share which browser you are testing in?

All 4 comments

@bhishp
Can you log what your s3Params looks like, minus the bucket name, when you are trying to upload an svg?

I recreated your sample (though just using http instead of express), and was able to create a valid url and upload with it.

// s3Params
{ Bucket: 'BUCKET',
  Key: 'test.svg',
  Expires: 60,
  ContentType: 'image/svg+xml',
  ACL: 'public-read' }

URL:

https://BUCKET.s3-us-west-2.amazonaws.com/test.svg?AWSAccessKeyId=ID&Content-Type=image%2Fsvg%2Bxml&Expires=1473874113&Signature=l57f1QQzt7Ilq%2BnYR%2F%2BsurQNRZg%3D&x-amz-acl=public-read

In my url, the + is encoded as %2B, not a space.

Can you also share which browser you are testing in?

@bhishp
As for your side-note, I can confirm you don't need to specify the ContentType when using signatureVersion: 'v4' with S3. I'll have to investigate if it can also work with signatureVersion: 'v2' (default when you don't specify a signatureVersion for your S3 client, and the region supports both versions), or update the docs if it can't.

ah.. I don't know how I didn't see it before. Yes, the problem is that I wasn't encoding the file-type when GETing /sign-s3.

Before:

xhr.open('GET', /api/sign-s3?file-name=${file.name}&file-type=${file.type});

Creating a HTTP Request like this (the + subsequently being interpreted as a space):

http://localhost:3000/api/sign-s3?file-name=circle.svg&file-type=image/svg+xml

After:

xhr.open('GET', /api/sign-s3?file-name=${encodeURIComponent(file.name)}&file-type=${encodeURIComponent(file.type)});

Creating a HTTP request with + correctly encoded:

http://localhost:3000/api/sign-s3?file-name=circle-with-header.svg&file-type=image%2Fsvg%2Bxml

Side-note

Additionally, I can confirm that omitting ContentType is valid now that I have set signatureVersion: 'v4'

@chrisradek Thanks for your help and quick response! :+1: :tada:

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

Was this page helpful?
0 / 5 - 0 ratings