Skip to content

Commit ade35cd

Browse files
committed
add strip_content_disposition to copy command
1 parent 0fc0c26 commit ade35cd

File tree

5 files changed

+80
-22
lines changed

5 files changed

+80
-22
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ zipserver copy --key path/to/file.zip --target s3backup --dest-key renamed.zip
173173
# With HTML footer injection
174174
zipserver copy --key games/123/index.html --target s3backup \
175175
--html-footer '<script src="/analytics.js"></script>'
176+
177+
# Strip Content-Disposition header (allow inline rendering instead of download)
178+
zipserver copy --key uploads/game.html --dest-key html5games/123/index.html \
179+
--strip-content-disposition
176180
```
177181

178182
**HTTP API:**
@@ -194,6 +198,9 @@ curl -X POST "http://localhost:8090/copy" \
194198
-d "key=games/123/index.html" \
195199
-d "target=s3backup" \
196200
-d "html_footer=<script src=\"/analytics.js\"></script>"
201+
202+
# Strip Content-Disposition header (allow inline rendering instead of download)
203+
curl "http://localhost:8090/copy?key=uploads/game.html&dest_key=html5games/123/index.html&strip_content_disposition=true"
197204
```
198205

199206
Either `target` or `dest_key` (or both) must be provided. When copying within the same storage (no `target`), `dest_key` must differ from `key`.

main.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,13 @@ var (
5151
extractHtmlFooter = extractCmd.Flag("html-footer", "HTML snippet to append to all index.html files").String()
5252

5353
// Copy command
54-
copyCmd = app.Command("copy", "Copy a file to target storage or different key")
55-
copyKey = copyCmd.Flag("key", "Storage key to copy").Required().String()
56-
copyDestKey = copyCmd.Flag("dest-key", "Destination key (defaults to source key)").String()
57-
copyTarget = copyCmd.Flag("target", "Target storage name").String()
58-
copyBucket = copyCmd.Flag("bucket", "Expected bucket (optional verification)").String()
59-
copyHtmlFooter = copyCmd.Flag("html-footer", "HTML snippet to append to copied file").String()
54+
copyCmd = app.Command("copy", "Copy a file to target storage or different key")
55+
copyKey = copyCmd.Flag("key", "Storage key to copy").Required().String()
56+
copyDestKey = copyCmd.Flag("dest-key", "Destination key (defaults to source key)").String()
57+
copyTarget = copyCmd.Flag("target", "Target storage name").String()
58+
copyBucket = copyCmd.Flag("bucket", "Expected bucket (optional verification)").String()
59+
copyHtmlFooter = copyCmd.Flag("html-footer", "HTML snippet to append to copied file").String()
60+
copyStripContentDisposition = copyCmd.Flag("strip-content-disposition", "Remove Content-Disposition header from copied file").Bool()
6061

6162
// Delete command
6263
deleteCmd = app.Command("delete", "Delete files from storage")
@@ -232,11 +233,12 @@ func runCopy(config *zipserver.Config) {
232233
ops := zipserver.NewOperations(config)
233234

234235
params := zipserver.CopyParams{
235-
Key: *copyKey,
236-
DestKey: *copyDestKey,
237-
TargetName: *copyTarget,
238-
ExpectedBucket: *copyBucket,
239-
HtmlFooter: *copyHtmlFooter,
236+
Key: *copyKey,
237+
DestKey: *copyDestKey,
238+
TargetName: *copyTarget,
239+
ExpectedBucket: *copyBucket,
240+
HtmlFooter: *copyHtmlFooter,
241+
StripContentDisposition: *copyStripContentDisposition,
240242
}
241243

242244
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(config.JobTimeout))

zipserver/copy_handler.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,14 @@ func (o *Operations) Copy(ctx context.Context, params CopyParams) CopyResult {
7474

7575
mReader := newMeasuredReader(finalReader)
7676

77+
contentDisposition := ""
78+
if !params.StripContentDisposition {
79+
contentDisposition = headers.Get("Content-Disposition")
80+
}
81+
7782
opts := PutOptions{
7883
ContentType: contentType,
79-
ContentDisposition: headers.Get("Content-Disposition"),
84+
ContentDisposition: contentDisposition,
8085
ContentEncoding: contentEncoding,
8186
ACL: ACLPublicRead,
8287
}
@@ -181,13 +186,15 @@ func copyHandler(w http.ResponseWriter, r *http.Request) error {
181186

182187
expectedBucket, _ := getParam(params, "bucket")
183188
htmlFooter := params.Get("html_footer")
189+
stripContentDisposition := params.Get("strip_content_disposition") == "true"
184190

185191
copyParams := CopyParams{
186-
Key: key,
187-
DestKey: destKey,
188-
TargetName: targetName,
189-
ExpectedBucket: expectedBucket,
190-
HtmlFooter: htmlFooter,
192+
Key: key,
193+
DestKey: destKey,
194+
TargetName: targetName,
195+
ExpectedBucket: expectedBucket,
196+
HtmlFooter: htmlFooter,
197+
StripContentDisposition: stripContentDisposition,
191198
}
192199
if err := copyParams.Validate(globalConfig); err != nil {
193200
return err

zipserver/copy_handler_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,47 @@ func TestCopy_PreservesHeaders(t *testing.T) {
195195
})
196196
}
197197

198+
func TestCopy_StripContentDisposition(t *testing.T) {
199+
withGoogleCloudStorage(t, func(storage Storage, config *Config) {
200+
ctx := context.Background()
201+
targetName := "mem-target-copy-strip-disposition"
202+
defer ClearNamedMemStorage(targetName)
203+
204+
config.StorageTargets = []StorageConfig{{
205+
Name: targetName,
206+
Type: Mem,
207+
Bucket: "target-bucket",
208+
}}
209+
210+
testKey := "zipserver_test/copy_strip_disposition_test.html"
211+
_, err := storage.PutFile(ctx, config.Bucket, testKey,
212+
strings.NewReader("<html><body>test</body></html>"), PutOptions{
213+
ContentType: "text/html",
214+
ContentDisposition: "attachment; filename=\"game.html\"",
215+
ACL: ACLPublicRead,
216+
})
217+
require.NoError(t, err)
218+
219+
ops := NewOperations(config)
220+
result := ops.Copy(ctx, CopyParams{
221+
Key: testKey,
222+
TargetName: targetName,
223+
StripContentDisposition: true,
224+
})
225+
226+
require.NoError(t, result.Err)
227+
228+
// Verify Content-Type preserved but Content-Disposition stripped
229+
targetStorage := GetNamedMemStorage(targetName)
230+
reader, headers, err := targetStorage.GetFile(ctx, "target-bucket", testKey)
231+
require.NoError(t, err)
232+
defer reader.Close()
233+
234+
assert.Equal(t, "text/html", headers.Get("Content-Type"))
235+
assert.Empty(t, headers.Get("Content-Disposition"))
236+
})
237+
}
238+
198239
func TestCopy_WithHtmlFooter(t *testing.T) {
199240
withGoogleCloudStorage(t, func(storage Storage, config *Config) {
200241
ctx := context.Background()

zipserver/operations.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@ type ExtractResult struct {
3030

3131
// CopyParams contains parameters for the copy operation
3232
type CopyParams struct {
33-
Key string // Storage key to copy
34-
DestKey string // Optional: destination key (defaults to Key if empty)
35-
TargetName string // Optional: target storage name (if empty, copies within primary storage)
36-
ExpectedBucket string // Optional: expected bucket for validation
37-
HtmlFooter string // Optional: HTML to append to index.html files
33+
Key string // Storage key to copy
34+
DestKey string // Optional: destination key (defaults to Key if empty)
35+
TargetName string // Optional: target storage name (if empty, copies within primary storage)
36+
ExpectedBucket string // Optional: expected bucket for validation
37+
HtmlFooter string // Optional: HTML to append to index.html files
38+
StripContentDisposition bool // Optional: if true, don't copy Content-Disposition header
3839
}
3940

4041
// DestKeyOrKey returns the destination key, defaulting to Key when DestKey is empty.

0 commit comments

Comments
 (0)