17

I have an S3 bucket having PDF files as objects and all of them are private. I create an S3 Presigned URL programmatically to get the object. It works fine. Now, I want it to be previewable as a PDF. Every object already has a Content-Type header set to application/pdf. Now, if I set the response-content-disposition header as a query parameter, it gets set but doesn't override the already existing Content-disposition header, instead, it creates a new one. If I set the Content-Disposition header in the metadata of the S3 object instead of adding it in the S3 Presigned URL as a query parameter, it still shows 2 headers. Is this some kind of bug on the AWS S3 side?

Below is the screenshot of the Response Header for reference.

Con

Any help will be much appreciated. Thanks.

Abdullah Khawer
  • 4,461
  • 4
  • 29
  • 66

2 Answers2

28

I have solved this issue using the latest API available for this thing from AWS SDK for NodeJS using the following code:

const aws = require('aws-sdk');

const AWS_SIGNATURE_VERSION = 'v4';

const s3 = new aws.S3({
  accessKeyId: <aws-access-key>,
  secretAccessKey: <aws-secret-access-key>,
  region: <aws-region>,
  signatureVersion: AWS_SIGNATURE_VERSION
});

/**
 * Return a signed document URL given a Document instance
 * @param  {object} document Document
 * @return {string}          Pre-signed URL to document in S3 bucket
 */
const getS3SignedDocumentURL = (docName) => {
  const url = s3.getSignedUrl('getObject', {
    Bucket: <aws-s3-bucket-name>,
    Key: <aws-s3-object-key>,
    Expires: <url-expiry-time-in-seconds>,
    ResponseContentDisposition: `attachment; filename="${docName}"`
  });

  return url;
};

/**
 * Return a signed document URL previewable given a Document instance
 * @param  {object} document Document
 * @return {string}          Pre-signed URL to previewable document in S3 bucket
 */
const getS3SignedDocumentURLPreviewable = (docName) => {
  const url = s3.getSignedUrl('getObject', {
    Bucket: <aws-s3-bucket-name>,
    Key: <aws-s3-object-key>,
    Expires: <url-expiry-time-in-seconds>,
    ResponseContentDisposition: `inline; filename="${docName}"`
  });

  return url;
};

module.exports = {
  getS3SignedDocumentURL,
  getS3SignedDocumentURLPreviewable
};

Note: Don't forget to replace the placeholders (<...>) with actual values to make it work.

Abdullah Khawer
  • 4,461
  • 4
  • 29
  • 66
  • 1
    Thanks for the solution, I have read an aws document here -> https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html#API_GetObject_RequestSyntax and it says the response header can be override, I tried but it's not working. Your solution is totally working for me. – Lolo. Oct 14 '20 at 08:52
  • @Lolo., My pleasure. :) – Abdullah Khawer Oct 14 '20 at 08:58
  • is there any document mentioned what parameter can developer pass into the getSignedUrl? I'm reading the SDK document (https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property) and it's not mention it – Lolo. Oct 14 '20 at 09:10
  • 1
    @Lolo., The document link that you have shared says the following: "See the given operation for the expected operation parameters." If the operation is "getObject" then the possible parameters can be found here: https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property – Abdullah Khawer Oct 14 '20 at 09:41
3

It's strange how often we overlook things like filenames where comma (,) could be common if it is user generated name. While setting response-content-disposition make sure to strip special character or properly escape the filename attribute

Refer https://stackoverflow.com/a/6745788/8813684 for more details

Naresh Kumar
  • 1,706
  • 1
  • 14
  • 26