Daveo
Daveo

Reputation: 19872

Amazon S3 Change file download name

I have files stored on S3 with a GUID as the key name.

I am using a pre signed URL to download as per S3 REST API

I store the original file name in my own Database. When a user clicks to download a file from my web application I want to return their original file name, but currently all they get is a GUID. How can I achieve this?

My web app is in salesforce so I do not have much control to do response.redirects all download the file to the web server then rename it due to governor limitations.

Is there some HTML redirect, meta refresh, Javascript I can use? Is there some way to change the download file name for S3 (the only thing I can think of is coping the object to a new name, downloading it, then deleting it).

I want to avoid creating a bucket per user as we will have a lot of users and still no guarantee each file with in each bucket will have a unique name

Any other solutions?

Upvotes: 171

Views: 90623

Answers (11)

Alberto Favaro
Alberto Favaro

Reputation: 1842

When work with AWS SDK for Java 2.x you can follow this guide for Work with Amazon S3 presigned URLs. Regardless of the request's model you build you can set Content-Disposition header by using this methods

Examples

PutObjectRequest

...
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
    .bucket(bucketName)
    .key(keyName)
    .contentType(contentType)
    .metadata(metadata)
    .contentDisposition("attachment;filename=" + displayName)
    .build();
        
PutObjectPresignRequest putObjectPresignRequest = PutObjectPresignRequest.builder()
        .signatureDuration(Duration.ofMinutes(10))
        .putObjectRequest(putObjectRequest)
        .build();

PresignedPutObjectRequest presignedPutObjectRequest = presigner.presignPutObject(putObjectPresignRequest);
...

GetObjectRequest

...
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
   .bucket(bucketName)
   .key(keyName)
   .responseContentDisposition("attachment;filename=" + displayName)
   .build();

GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder()
   .signatureDuration(Duration.ofMinutes(60))
   .getObjectRequest(getObjectRequest)
   .build();

PresignedGetObjectRequest presignedGetObjectRequest = presigner.presignGetObject(getObjectPresignRequest);
...

Not tested by I think this is the right way for Java client.

Upvotes: 2

Siddhant_Jain10
Siddhant_Jain10

Reputation: 61

Faced similar issue. Need to generate pre-signed url to download pdf from s3 but with different name other that what present in s3 bucket. This got resolved by using :-

Using Python boto3

file_download_url = client.generate_presigned_url(
ClientMethod = "get_object",
ExpiresIn = 3600,
Params = {
    "Bucket": "s3_bucket_name",
    "Key": "s3_key",
    "ResponseContentDisposition": "attachment; filename=file_name.pdf",
    "ResponseContentType" : "application/pdf"
}

)

Reference :- https://github.com/boto/boto3/issues/356

Upvotes: 6

Jha Nitesh
Jha Nitesh

Reputation: 388

I spent a few hours to find this solution.

const { CloudFront } = require("aws-sdk");
const url = require("url");

const generateSingedCloudfrontUrl = (path) => {
  const cloudfrontAccessKeyId = process.env.CF_ACCESS_KEY;
  const cloudFrontPrivateKey = process.env.CF_PRIVATE_KEY;
  const formattedKey = `${"-----BEGIN RSA PRIVATE KEY-----"}\n${cloudFrontPrivateKey}\n${"-----END RSA PRIVATE KEY-----"}`;
  const signer = new CloudFront.Signer(cloudfrontAccessKeyId, formattedKey);
  //  12 hours
  const EXPIRY_TIME = 43200000;

  const domain = process.env.CF_DOMAIN;
  const signedUrl = signer.getSignedUrl({
    url: url.format(`https://${domain}/${path}`),
    expires: Math.floor((Date.now() + EXPIRY_TIME) / 1000),
  });
  return signedUrl;
};

const fileName = "myFile.png";
  const result = generateSingedCloudfrontUrl(
    `originals/orgs/originals/MSP/1539087e-02b7-414f-abc8-3542ee0c8420/1644588362499/Screenshot from 2022-02-09 16-29-04..png?response-content-disposition=${encodeURIComponent(
      `attachment; filename=${fileName}`
    )
});

Upvotes: 1

cloudberryman
cloudberryman

Reputation: 4698

I guess your cross posted this questions to Amazon S3 forum, but for the sake of others I'd like to post the answer here:

If there is only ever one "user filename" for each S3 object, then you can set the Content-Disposition header on your s3 file to set the downloading filename:

Content-Disposition: attachment; filename="foo.bar"

For the sake of fairness I'd like to mention that it was not me to provide the right answer on Amazon forum and all credits should go to Colin Rhodes ;-)

Upvotes: 124

Udi
Udi

Reputation: 30472

Using python and boto v2:

    conn = boto.connect_s3(
        AWS_ACCESS_KEY_ID,
        AWS_SECRET_ACCESS_KEY,
        host=settings.AWS_S3_HOST,
    )
    b = conn.get_bucket(BUCKET_NAME)
    key = b.get_key(path)
    url = key.generate_url(
        expires_in=60 * 60 * 10,  # expiry time is in seconds
        response_headers={
            "response-content-disposition": "attachment; filename=foo.bar"
        },
    )

Upvotes: 2

hkutluay
hkutluay

Reputation: 6944

With C# using AWSSDK,

GetPreSignedUrlRequest request = new GetPreSignedUrlRequest
{
    BucketName = BucketName,
    Key = Key,
    Expires = DateTime.Now.AddMinutes(25) 
};

request.ResponseHeaderOverrides.ContentDisposition = $"attachment; filename={FileName}";

var url = s3Client.GetPreSignedURL(request);

Upvotes: 13

Abhishek Basak
Abhishek Basak

Reputation: 111

For Java AWS SDK below Code Snippet should do the job:

GeneratePresignedUrlRequest generatePresignedUrlRequest = 
                new GeneratePresignedUrlRequest(s3Bucket, objectKey)
                .withMethod(HttpMethod.GET)
                .withExpiration(getExpiration());

ResponseHeaderOverrides responseHeaders = new ResponseHeaderOverrides();
responseHeaders.setContentDisposition("attachment; filename =\"" + fileName + "\"");

generatePresignedUrlRequest.setResponseHeaders(responseHeaders);

Upvotes: 5

Chris Mcdonald
Chris Mcdonald

Reputation: 29

It looks like :response_content_disposition is undocumented in the presigned_url method. This is what worked for me

    signer = Aws::S3::Presigner.new
    signer.presigned_url(:get_object, bucket: @bucket, key: filename, 
    response_content_disposition: "attachment; filename =#{new_name}")

Upvotes: 2

YugoAmaryl
YugoAmaryl

Reputation: 403

I have the same issue, I solved it by set http header "content-disposition" while submit the file to S3, the SDK version is AWS SDK for PHP 3.x. here is the doc http://docs.amazonaws.cn/en_us/aws-sdk-php/latest/api-s3-2006-03-01.html#putobject

a piece of my code

    public function __construct($config) 
    {
        $this->handle = new S3Client([
            'credentials' => array(
                'key' => $config['key'],
                'secret' => $config['secret'],
            ),
            ...
        ]);

        ...
    }

    public function putObject($bucket, $object_name, $source_file, $content_type = false, $acl = 'public-read', $filename = '')
    {
        try {
            $params = [
                'Bucket'      => $bucket,
                'Key'         => $object_name,
                'SourceFile'  => $source_file,
                'ACL'         => $acl,
            ];

            if ($content_type) $params['ContentType'] = $content_type;
            if ($filename) $params['ContentDisposition'] = 'attachment; filename="' . $filename . '"';

            $result = $this->handle->putObject($params);

            ...
        }
        catch(Exception $e)
        {
            ...
        }
    }

Upvotes: 0

Pawel
Pawel

Reputation: 3802

While the accepted answer is correct I find it very abstract and hard to utilize.

Here is a piece of node.js code that solves the problem stated. I advise to execute it as the AWS Lambda to generate pre-signed Url.

var AWS = require('aws-sdk');
var s3 = new AWS.S3({
    signatureVersion: 'v4'
});
const s3Url = process.env.BUCKET;

module.exports.main = (event, context, callback) => {
var s3key = event.s3key
var originalFilename = event.originalFilename

var url = s3.getSignedUrl('getObject', {
        Bucket: s3Url,
        Key: s3key,
        Expires: 600,
        ResponseContentDisposition: 'attachment; filename ="' + originalFilename + '"'
    });

[... rest of Lambda stuff...]

}

Please, take note of ResponseContentDisposition attribute of params object passed into s3.getSignedUrl function.

More information under getObject function doc at http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property

Upvotes: 108

Uriah Carpenter
Uriah Carpenter

Reputation: 6726

In early January 2011 S3 added request header overrides. This functionality allows you to 'dynamically' alter the Content-Disposition header for individual requests.

See the S3 documentation on getting objects for more details.

Upvotes: 29

Related Questions