Flutter :: Amazon S3 Image Upload

3,753
class UploadAWS {
  Future<String> uploadImage(File image, String path) async {
    final Dio _client = Dio();
    _client.interceptors.add(LogInterceptor());
    final int length = await image.length();
    final String filename = 'testFile.jpg';
    final String key = path + filename;
    final Policy policy = Policy.fromS3PreSignedPost(
      key,
      AWSBucket,
      15,
      AWSaccessKey,
      length,
      AWSsessionToken,
      region: AWSregion,
    );
    final List<int> signKey =
        SigV4.calculateSigningKey(AWSsecretKey, policy.datetime, AWSregion, AWSservice);
    final String signature = SigV4.calculateSignature(signKey, policy.encode());
    final FormData formData = FormData.fromMap({
      'key': policy.key,
      'acl': 'public-read',
      'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
      'X-Amz-Credential': policy.credential,
      'X-Amz-Date': policy.datetime,
      'Policy': policy.encode(),
      'X-Amz-Signature': signature,
      'x-amz-security-token': AWSsessionToken,
      'file': await MultipartFile.fromFile(
        image.path,
        filename: filename,
      )
    });
    Response response = await _client.post('https://$AWSBucket.s3.amazonaws.com', data: formData);
    _client.close();
    return response.headers.map['location'].first;
  }
}

class Policy {
  String expiration;
  String region;
  String bucket;
  String key;
  String credential;
  String datetime;
  String sessionToken;
  int maxFileSize;

  Policy(this.key, this.bucket, this.datetime, this.expiration, this.credential,
      this.maxFileSize, this.sessionToken,
      {this.region = AWS_REGION_FTALK});

  factory Policy.fromS3PreSignedPost(
    String key,
    String bucket,
    int expiryMinutes,
    String accessKeyId,
    int maxFileSize,
    String sessionToken, {
    String region,
  }) {
    final datetime = SigV4.generateDatetime();
    final expiration = (DateTime.now())
        .add(Duration(minutes: expiryMinutes))
        .toUtc()
        .toString()
        .split(' ')
        .join('T');
    final cred =
        '$accessKeyId/${SigV4.buildCredentialScope(datetime, region, 's3')}';
    final policy = Policy(
        key, bucket, datetime, expiration, cred, maxFileSize, sessionToken,
        region: region);
    return policy;
  }

  String encode() {
    final bytes = utf8.encode(toString());
    return base64.encode(bytes);
  }

  @override
  String toString() {
    return '''
{ "expiration": "${this.expiration}",
  "conditions": [
    {"bucket": "${this.bucket}"},
    ["starts-with", "\$key", "${this.key}"],
    {"acl": "public-read"},
    ["content-length-range", 1, ${this.maxFileSize}],
    {"x-amz-credential": "${this.credential}"},
    {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
    {"x-amz-date": "${this.datetime}" },
    {"x-amz-security-token": "${this.sessionToken}" }
  ]
}
''';
  }
}

With this code you can send the image, but unfortunately I can't change the file's metadata when making the request, making it binary/octet-stream instead of image/jpeg, this is only a problem if you use the image outside the application.

In this implementation I used the Dio and Amazon Cognito Identity Dart packages, you can use either Amazon Cognito Identity Dart or Amazon Cognito Identity Dart 2, I recommend the second option, as the repository is still active.

UPDATE, FIXED BINARY/OCTET-STREAM PROBLEM

class UploadAWS {
  Future<String> uploadImage(File image, String path) async {
    final Dio _client = Dio();
    _client.interceptors.add(LogInterceptor());
    final int length = await image.length();
    final String filename = 'testFile.jpg';
    final String key = path + filename;
    final Policy policy = Policy.fromS3PreSignedPost(
      key,
      AWSBucket,
      15,
      AWSaccessKey,
      length,
      AWSsessionToken,
      region: AWSregion,
    );
    final List<int> signKey =
        SigV4.calculateSigningKey(AWSsecretKey, policy.datetime, AWSregion, AWSservice);
    final String signature = SigV4.calculateSignature(signKey, policy.encode());
    final FormData formData = FormData.fromMap({
      'key': policy.key,
      'acl': 'public-read',
      'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
      'X-Amz-Credential': policy.credential,
      'X-Amz-Date': policy.datetime,
      'Policy': policy.encode(),
      'X-Amz-Signature': signature,
      'x-amz-security-token': AWSsessionToken,
      'Content-Type': 'image/jpeg',
      'file': await MultipartFile.fromFile(
        image.path,
        filename: filename,
      )
    });
    Response response = await _client.post('https://$AWSBucket.s3.amazonaws.com', data: formData);
    _client.close();
    return response.headers.map['location'].first;
  }
}

class Policy {
  String expiration;
  String region;
  String bucket;
  String key;
  String credential;
  String datetime;
  String sessionToken;
  int maxFileSize;

  Policy(this.key, this.bucket, this.datetime, this.expiration, this.credential,
      this.maxFileSize, this.sessionToken,
      {this.region = AWS_REGION_FTALK});

  factory Policy.fromS3PreSignedPost(
    String key,
    String bucket,
    int expiryMinutes,
    String accessKeyId,
    int maxFileSize,
    String sessionToken, {
    String region,
  }) {
    final datetime = SigV4.generateDatetime();
    final expiration = (DateTime.now())
        .add(Duration(minutes: expiryMinutes))
        .toUtc()
        .toString()
        .split(' ')
        .join('T');
    final cred =
        '$accessKeyId/${SigV4.buildCredentialScope(datetime, region, 's3')}';
    final policy = Policy(
        key, bucket, datetime, expiration, cred, maxFileSize, sessionToken,
        region: region);
    return policy;
  }

  String encode() {
    final bytes = utf8.encode(toString());
    return base64.encode(bytes);
  }

  @override
  String toString() {
    return '''
{ "expiration": "${this.expiration}",
  "conditions": [
    {"bucket": "${this.bucket}"},
    ["starts-with", "\$key", "${this.key}"],
    ["starts-with", "\$Content-Type", "image/"],
    {"acl": "public-read"},
    ["content-length-range", 1, ${this.maxFileSize}],
    {"x-amz-credential": "${this.credential}"},
    {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
    {"x-amz-date": "${this.datetime}" },
    {"x-amz-security-token": "${this.sessionToken}" }
  ]
}
''';
  }
}
Share:
3,753
Vishal Dhaduk
Author by

Vishal Dhaduk

Updated on December 11, 2022

Comments

  • Vishal Dhaduk
    Vishal Dhaduk over 1 year

    https://pub.dartlang.org/packages/flutter_amazon_s3

    I try to upload Image using this lib in flutter to Amazon S3 but not working any solution?

    import 'package:flutter_amazon_s3/flutter_amazon_s3.dart';
    
    String uploadedImageUrl = await FlutterAmazonS3.uploadImage(
          _image.path, BUCKET_NAME, IDENTITY_POOL_ID);
    
    print("_uploadImageToAWS uploadedImageUrl ::" + uploadedImageUrl);
    

    but nothing return in log, even no any error.

    • Jawand Singh
      Jawand Singh almost 5 years
      can you share, the code you have written and the errors you are getting? That will help everyone.
    • Jignesh Patel
      Jignesh Patel almost 4 years
      @Vishal Dhaduk Did you find to upload image on s3? because i implemented it but i got this exception below Unhandled Exception: PlatformException(error, Can't downgrade database from version 6 to 5, null) Can you help me to solved out?
  • Sukhi
    Sukhi over 4 years
    Welcome to Stackoverflow ! This is not an answer, Priyanka; it's a comment. You might want to delete your answer. Moreover, your code looks similar Vishal's one.