Setting ContentDisposition and CacheControl do not change the response headers of the uploaded s3 object on retrieval.
const s3Params = {
Bucket: config.s3_match_bucket,
Key: key,
Expires: 60,
CacheControl: 'public, max-age=31536000',
ContentDisposition: 'attachment',
ContentType: file_type,
ACL: 'public-read'
};
s3.getSignedUrl('putObject', s3Params, (err, data) => {
if(err){
console.log(err);
return res.end();
}
res.send( { signedRequest: data, key: key } );
});
Hi @ToddAlvord
I suspect this is due to the signature verification mechanism of S3 server.
Basically how the pre-signed URL (see SigV2 for more information) works is that the signer signs each header and hash them with other fields(eg. date) and generate a signature, and then send them all together to S3 server, in a URL query. When the server receives such request, it will also extract the same fields as in the request and generate a signature and compare with that in the request.
However, on the server side, when it extract headers, some headers may get ignored from URL query. In your case, Cache-Control and Content-Type headers are both ignored.
If you want these headers to be seen by S3 server, you could specify them in request header when using the URL. If you are using Node, your code may be like below:
s3.getSignedUrl('putObject', params, (err, data) => {
if(err){
console.log(err);
return;
}
var options = url.parse(data);
options.method = 'PUT';
options.headers = {
"content-length": TEST_STRING.length, //since we use stream here, specifying length is needed
"cache-control": 'public, max-age=31536000',
"content-disposition": 'inline',
};
var req = https.request(options, function (res) {
//do something
});
req.on('error', function (err) {
console.log(err);
})
req.write(TEST_STRING);
req.end();
});
This looks related to #1313.
As a workaround, headers can be added to the request after it has been signed. If the upload is being sent from a browser, CacheControl and ContentDisposition are supported in POST uploads to S3. The SDK's S3 client includes a helper method for creating and signing POST upload policies.
The sig v4 spec, it is possible to specify content-type. although it seems after setting signatureVersion: 'v4' on the S3 object and setting the signed url param ContentType, it is possible to upload any content type via the signed url. It seems the ContentType is not actually part of the signature.
I assume this is incorrect?
It sounds similar to the go SDK issue https://github.com/aws/aws-sdk-go/issues/503
Thanks
In https://github.com/aws/aws-sdk-js/blob/488327a234ddef160a1592e717dd1e437560119a/lib/signers/v4.js#L190
it looks like content-type is not a signable header, but it should be from what I understand. Can someone verify this? If I've taken this off topic I'll start a new issue.
Thanks
Hi @jonface
Yes. The content-type is not a signable header and will be ignored when signing the headers. This is mainly because of the fact that the S3 ignores this header, so adding this to query string before signing will lead to the incorrect signature exception.
Although using sigv4, S3 has many customizations on it. Different SDKs may have different implementations. What Go does is removing all these headers out of query string which gets rid of all the hesitation that which header could be ignored by S3.
Hi @AllanFly120 thanks for the reply. I'm still confused, specifically sig v4 only,
If the Content-Type header is present in the request, you must add it to the CanonicalHeaders list
All the headers in CanonicalHeaders get signed if I'm not mistaken, so content-type is optional, but it is still a signed header if available (in contrast to the SDK which ignores it).
But from what I can see, the AWS SDK JS removes the content-type header, even though it should be signed for v4, if present. Because of this, the final signed url, allows the user to upload any content-type as it was removed from the signature generation.
Surely content-type just needs removing from unsignableHeaders for v4 signatures?
From what I can see, the AWS SDK JS is not following the sig v4 spec.
Thanks again
@jonface
I had to go way back, but it looks like both Content-Type and User-Agent were made 'unsignable' when the browser version of the SDK was developed. Presumably this is because browsers could modify these headers. For example, there was a bug reported years ago that Firefox would always add the charset to the Content-Type, even if it wasn't set in the SDK.
It may not be possible to change this with the current version of the SDK as that could break current consumers of the SDK, taht'll require further investigation. This is definitely something we will want to revisit though when we do a major version bump.
ContentDisposition: 'attachment' will not work for putObject you need to use getObject with 'ResponseContentDisposition': 'attachment; filename=file.txt'
nodejs
const params = {
Bucket: 'xxxxx',
Key: '2017/may/tuesday/file.txt',
ResponseContentDisposition: 'attachment; filename=file.txt'
};
const response = s3.getSignedUrl('getObject', params);
while downloading we ask s3 to add content-disposition header as attachment.for that we need to pass ResponseContentDisposition while creating presigned url for getObject
Until (if at all) this is fixed, it would be great to document list of the params which are accepted by SDK but are actually not included into the signed URL. I would expect that to be detailed in the section.
Most helpful comment
ContentDisposition: 'attachment' will not work for putObject you need to use getObject with 'ResponseContentDisposition': 'attachment; filename=file.txt'
nodejs
while downloading we ask s3 to add content-disposition header as attachment.for that we need to pass ResponseContentDisposition while creating presigned url for getObject