From 203c8c4a81e4e57fc6717222a9b62e76e9874d24 Mon Sep 17 00:00:00 2001
From: develar <develar@gmail.com>
Date: Wed, 12 Jul 2017 10:14:13 +0200
Subject: [PATCH] feat(s3): Ability to not add ` "x-amz-acl": "public-read"` to
 the header when uploading artefacts to S3 bucket

Close #1822, Close #1618
---
 docs/Publishing Artifacts.md                  |  4 +++-
 .../src/publishOptions.ts                     |  5 +++-
 .../electron-publisher-s3/src/s3Publisher.ts  | 22 +++++++++++++----
 .../electron-publisher-s3/src/uploader.ts     | 24 +++++++------------
 4 files changed, 33 insertions(+), 22 deletions(-)

diff --git a/docs/Publishing Artifacts.md b/docs/Publishing Artifacts.md
index 44cd39aa488..9222345dc7d 100644
--- a/docs/Publishing Artifacts.md	
+++ b/docs/Publishing Artifacts.md	
@@ -143,7 +143,9 @@ Or in the [~/.aws/credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/
 * <a name="S3Options-path"></a>`path` = `/` String - The directory path.
 * <a name="S3Options-region"></a>`region` String - The region. Is determined and set automatically when publishing.
 * <a name="S3Options-channel"></a>`channel` = `latest` String - The channel.
-* <a name="S3Options-acl"></a>`acl` = `public-read` "private" | "public-read" - The ACL.
+* <a name="S3Options-acl"></a>`acl` = `public-read` "private" | "public-read" - The ACL. Set to `null` to not [add](https://github.com/electron-userland/electron-builder/issues/1822).
+  
+  Please see [required permissions for the S3 provider](https://github.com/electron-userland/electron-builder/issues/1618#issuecomment-314679128).
 * <a name="S3Options-storageClass"></a>`storageClass` = `STANDARD` "STANDARD" | "REDUCED_REDUNDANCY" | "STANDARD_IA" - The type of storage to use for the object.
 * <a name="S3Options-provider"></a>**`provider`** "github" | "bintray" | "s3" | "generic" - The provider.
 
diff --git a/packages/electron-builder-http/src/publishOptions.ts b/packages/electron-builder-http/src/publishOptions.ts
index 2967d1a40c7..959ebc1e47e 100644
--- a/packages/electron-builder-http/src/publishOptions.ts
+++ b/packages/electron-builder-http/src/publishOptions.ts
@@ -116,7 +116,10 @@ export interface S3Options extends PublishConfiguration {
   readonly channel?: string | null
 
   /**
-   * The ACL.
+   * The ACL. Set to `null` to not [add](https://github.com/electron-userland/electron-builder/issues/1822).
+   *
+   * Please see [required permissions for the S3 provider](https://github.com/electron-userland/electron-builder/issues/1618#issuecomment-314679128).
+   *
    * @default public-read
    */
   readonly acl?: "private" | "public-read" | null
diff --git a/packages/electron-publisher-s3/src/s3Publisher.ts b/packages/electron-publisher-s3/src/s3Publisher.ts
index 39d3b554e17..6aa8b0d242a 100644
--- a/packages/electron-publisher-s3/src/s3Publisher.ts
+++ b/packages/electron-publisher-s3/src/s3Publisher.ts
@@ -1,12 +1,14 @@
 import { S3 } from "aws-sdk"
+import { CreateMultipartUploadRequest, ObjectCannedACL, StorageClass } from "aws-sdk/clients/s3"
 import { S3Options } from "electron-builder-http/out/publishOptions"
 import { debug } from "electron-builder-util"
 import { PublishContext, Publisher } from "electron-publish"
 import { ProgressCallback } from "electron-publish/out/progress"
 import { ensureDir, stat, symlink } from "fs-extra-p"
+import mime from "mime"
 import * as path from "path"
 import { basename } from "path"
-import { S3Client } from "./uploader"
+import { S3Client, Uploader } from "./uploader"
 
 export default class S3Publisher extends Publisher {
   readonly providerName = "S3"
@@ -50,11 +52,21 @@ export default class S3Publisher extends Publisher {
       return
     }
 
-    const uploader = client.createFileUploader(file, target, {
+    const s3Options: CreateMultipartUploadRequest  = {
+      Key: target,
       Bucket: this.info.bucket!,
-      ACL: this.info.acl || "public-read",
-      StorageClass: this.info.storageClass || undefined
-    })
+      ContentType: mime.lookup(file)
+    }
+
+    // if explicitly set to null, do not add
+    if (this.info.acl !== null) {
+      s3Options.ACL = this.info.acl as ObjectCannedACL || "public-read"
+    }
+    if (this.info.storageClass != null) {
+      s3Options.StorageClass = this.info.storageClass as StorageClass
+    }
+
+    const uploader = new Uploader(client, s3Options, file, fileStat)
 
     const progressBar = this.createProgressBar(fileName, fileStat)
     if (progressBar != null) {
diff --git a/packages/electron-publisher-s3/src/uploader.ts b/packages/electron-publisher-s3/src/uploader.ts
index 2375328a5d2..e60d9400109 100644
--- a/packages/electron-publisher-s3/src/uploader.ts
+++ b/packages/electron-publisher-s3/src/uploader.ts
@@ -1,9 +1,9 @@
 import { config as awsConfig, S3 } from "aws-sdk"
+import { CreateMultipartUploadRequest } from "aws-sdk/clients/s3"
 import BluebirdPromise from "bluebird-lst"
 import { createHash } from "crypto"
 import { EventEmitter } from "events"
-import { createReadStream, stat } from "fs-extra-p"
-import mime from "mime"
+import { createReadStream, Stats } from "fs-extra-p"
 import { cpus } from "os"
 
 const MAX_PUT_OBJECT_SIZE = 5 * 1024 * 1024 * 1024
@@ -45,10 +45,6 @@ export class S3Client {
       throw new Error("Maximum multipartUploadSize is 5GB.")
     }
   }
-
-  createFileUploader(localFile: string, target: string, s3Options: any) {
-    return new Uploader(this, {Key: target, ...s3Options}, localFile)
-  }
 }
 
 export class Uploader extends EventEmitter {
@@ -57,16 +53,15 @@ export class Uploader extends EventEmitter {
 
   private cancelled = false
 
-  /** @readonly */
-  contentLength: number
+  readonly contentLength: number
 
-  constructor(private readonly client: S3Client, private readonly s3Options: any, private readonly localFile: string) {
+  constructor(private readonly client: S3Client, private readonly s3Options: CreateMultipartUploadRequest, private readonly localFile: string, localFileStat: Stats) {
     super()
+
+    this.contentLength = localFileStat.size
   }
 
   async upload() {
-    this.contentLength = (await stat(this.localFile)).size
-
     const client = this.client
     if (this.contentLength < client.multipartUploadThreshold) {
       const md5 = await hashFile(this.localFile, "md5", "base64")
@@ -83,7 +78,7 @@ export class Uploader extends EventEmitter {
       throw new Error(`File size exceeds maximum object size: ${this.localFile}`)
     }
 
-    const data = await this.runOrRetry(() => client.s3.createMultipartUpload({ContentType: mime.lookup(this.localFile), ...this.s3Options}).promise())
+    const data = await this.runOrRetry(() => client.s3.createMultipartUpload(this.s3Options).promise())
     await this.multipartUpload(data.UploadId!, multipartUploadSize)
   }
 
@@ -95,10 +90,9 @@ export class Uploader extends EventEmitter {
     this.loaded = 0
     return new BluebirdPromise<any>((resolve, reject) => {
       this.client.s3.putObject({
-        ContentType: mime.lookup(this.localFile),
-        ContentLength: this.contentLength,
         Body: createReadStream(this.localFile),
-        ContentMD5: md5, ...this.s3Options
+        ContentMD5: md5,
+        ...this.s3Options,
       })
         .on("httpUploadProgress", progress => {
           this.loaded = progress.loaded