diff --git a/storage/client.go b/storage/client.go
index 4906b1d1f704..70b2a280e3b3 100644
--- a/storage/client.go
+++ b/storage/client.go
@@ -254,6 +254,9 @@ type openWriterParams struct {
// attrs - see `Writer.ObjectAttrs`.
// Required.
attrs *ObjectAttrs
+ // forceEmptyContentType - Disables auto-detect of Content-Type
+ // Optional.
+ forceEmptyContentType bool
// conds - see `Writer.o.conds`.
// Optional.
conds *Conditions
diff --git a/storage/http_client.go b/storage/http_client.go
index a0f3c00a7373..e3e0d761bb08 100644
--- a/storage/http_client.go
+++ b/storage/http_client.go
@@ -885,7 +885,7 @@ func (c *httpStorageClient) OpenWriter(params *openWriterParams, opts ...storage
mediaOpts := []googleapi.MediaOption{
googleapi.ChunkSize(params.chunkSize),
}
- if c := attrs.ContentType; c != "" {
+ if c := attrs.ContentType; c != "" || params.forceEmptyContentType {
mediaOpts = append(mediaOpts, googleapi.ContentType(c))
}
if params.chunkRetryDeadline != 0 {
diff --git a/storage/integration_test.go b/storage/integration_test.go
index 0278b7005078..1112f8f5f38e 100644
--- a/storage/integration_test.go
+++ b/storage/integration_test.go
@@ -2415,8 +2415,9 @@ func TestIntegration_WriterContentType(t *testing.T) {
multiTransportTest(ctx, t, func(t *testing.T, ctx context.Context, bucket, _ string, client *Client) {
obj := client.Bucket(bucket).Object("content")
testCases := []struct {
- content string
- setType, wantType string
+ content string
+ setType, wantType string
+ forcedEmptyContentType bool
}{
{
// Sniffed content type.
@@ -2438,9 +2439,17 @@ func TestIntegration_WriterContentType(t *testing.T) {
setType: "image/jpeg",
wantType: "image/jpeg",
},
+ {
+ // Content type sniffing disabled.
+ content: "
My first page",
+ forcedEmptyContentType: true,
+ },
}
for i, tt := range testCases {
- if err := writeObject(ctx, obj, tt.setType, []byte(tt.content)); err != nil {
+ writer := newWriter(ctx, obj, tt.setType)
+ writer.ForceEmptyContentType = tt.forcedEmptyContentType
+
+ if err := writeContents(writer, []byte(tt.content)); err != nil {
t.Errorf("writing #%d: %v", i, err)
}
attrs, err := obj.Attrs(ctx)
@@ -5649,10 +5658,7 @@ func deleteObjectIfExists(o *ObjectHandle, retryOpts ...RetryOption) error {
return nil
}
-func writeObject(ctx context.Context, obj *ObjectHandle, contentType string, contents []byte) error {
- w := obj.Retryer(WithPolicy(RetryAlways)).NewWriter(ctx)
- w.ContentType = contentType
-
+func writeContents(w *Writer, contents []byte) error {
if contents != nil {
if _, err := w.Write(contents); err != nil {
_ = w.Close()
@@ -5662,6 +5668,19 @@ func writeObject(ctx context.Context, obj *ObjectHandle, contentType string, con
return w.Close()
}
+func writeObject(ctx context.Context, obj *ObjectHandle, contentType string, contents []byte) error {
+ w := newWriter(ctx, obj, contentType)
+
+ return writeContents(w, contents)
+}
+
+func newWriter(ctx context.Context, obj *ObjectHandle, contentType string) *Writer {
+ w := obj.Retryer(WithPolicy(RetryAlways)).NewWriter(ctx)
+ w.ContentType = contentType
+
+ return w
+}
+
func readObject(ctx context.Context, obj *ObjectHandle) ([]byte, error) {
r, err := obj.NewReader(ctx)
if err != nil {
diff --git a/storage/writer.go b/storage/writer.go
index aeb7ed418c8d..43a0f0d10937 100644
--- a/storage/writer.go
+++ b/storage/writer.go
@@ -88,6 +88,11 @@ type Writer struct {
// cancellation.
ChunkRetryDeadline time.Duration
+ // ForceEmptyContentType is an optional parameter that is used to disable
+ // auto-detection of Content-Type. By default, if a blank Content-Type
+ // is provided, then gax.DetermineContentType is called to sniff the type.
+ ForceEmptyContentType bool
+
// ProgressFunc can be used to monitor the progress of a large write
// operation. If ProgressFunc is not nil and writing requires multiple
// calls to the underlying service (see
@@ -180,18 +185,19 @@ func (w *Writer) openWriter() (err error) {
isIdempotent := w.o.conds != nil && (w.o.conds.GenerationMatch >= 0 || w.o.conds.DoesNotExist == true)
opts := makeStorageOpts(isIdempotent, w.o.retry, w.o.userProject)
params := &openWriterParams{
- ctx: w.ctx,
- chunkSize: w.ChunkSize,
- chunkRetryDeadline: w.ChunkRetryDeadline,
- bucket: w.o.bucket,
- attrs: &w.ObjectAttrs,
- conds: w.o.conds,
- encryptionKey: w.o.encryptionKey,
- sendCRC32C: w.SendCRC32C,
- donec: w.donec,
- setError: w.error,
- progress: w.progress,
- setObj: func(o *ObjectAttrs) { w.obj = o },
+ ctx: w.ctx,
+ chunkSize: w.ChunkSize,
+ chunkRetryDeadline: w.ChunkRetryDeadline,
+ bucket: w.o.bucket,
+ attrs: &w.ObjectAttrs,
+ conds: w.o.conds,
+ encryptionKey: w.o.encryptionKey,
+ sendCRC32C: w.SendCRC32C,
+ donec: w.donec,
+ setError: w.error,
+ progress: w.progress,
+ setObj: func(o *ObjectAttrs) { w.obj = o },
+ forceEmptyContentType: w.ForceEmptyContentType,
}
if err := w.ctx.Err(); err != nil {
return err // short-circuit