Skip to content

Commit

Permalink
blaze: support unescaped characters in utf-8 file name from content d…
Browse files Browse the repository at this point in the history
…isposition
  • Loading branch information
256dpi committed Sep 8, 2023
1 parent 4d52241 commit 79816c3
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 6 deletions.
2 changes: 1 addition & 1 deletion blaze/bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ func (b *Bucket) uploadBody(ctx *fire.Context, mediaType string) ([]string, erro

// parse content disposition
if ctx.HTTPRequest.Header.Get("Content-Disposition") != "" {
disposition, params, err := mime.ParseMediaType(ctx.HTTPRequest.Header.Get("Content-Disposition"))
disposition, params, err := parseContentDisposition(ctx.HTTPRequest.Header.Get("Content-Disposition"))
if err != nil {
return nil, err
}
Expand Down
7 changes: 2 additions & 5 deletions blaze/bucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package blaze
import (
"bytes"
"context"
"mime"
"mime/multipart"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -189,9 +188,7 @@ func TestBucketUploadActionExtended(t *testing.T) {
assert.Equal(t, "expected attachment content disposition", res.Body.String())

req.Header.Set("Content-Length", "12")
req.Header.Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{
"filename": "火.txt",
}))
req.Header.Set("Content-Disposition", "attachment; filename*=utf-8''%E7%81%AB%20(!).txt")

res, err = tester.RunAction(&fire.Context{
Operation: fire.CollectionAction,
Expand All @@ -207,7 +204,7 @@ func TestBucketUploadActionExtended(t *testing.T) {
Base: files[0].Base,
State: Uploaded,
Updated: files[0].Updated,
Name: "火.txt",
Name: "火 (!).txt",
Type: "text/plain",
Size: 12,
Service: "default",
Expand Down
30 changes: 30 additions & 0 deletions blaze/utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package blaze

import (
"errors"
"mime"
"net/url"
"reflect"
"strings"

"github.com/256dpi/fire/coal"
)
Expand All @@ -23,3 +27,29 @@ func collectFields(model coal.Model) []string {

return list
}

func parseContentDisposition(str string) (string, map[string]string, error) {
// attempt to parse string
typ, params, err := mime.ParseMediaType(str)
if err == nil || !errors.Is(err, mime.ErrInvalidMediaParameter) {
return typ, params, err
}

/* error may be due to unescaped characters */

// Chrome will generate the following Content-Disposition header for a file
// named "file_$%!&*()[]{}^+=#@`,;'-_"`.mp4":
// "attachment; filename*=utf-8''file_$%25!&*()%5B%5D%7B%7D%5E+=#@%60,;'-_%22%60.mp4"

// therefore the following characters need pre-encoding without "=", "*" and ";" to please the currently
// implemented decoding algorithm in Go
special := []string{"$", "!", "&", "(", ")", "+", "#", "@", ",", "-", "_"}
var args = make([]string, 0, len(special)*2)
for _, s := range special {
args = append(args, s, url.QueryEscape(s))
}
rep := strings.NewReplacer(args...)
str = rep.Replace(str)

return mime.ParseMediaType(str)
}
31 changes: 31 additions & 0 deletions blaze/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"testing"

"github.com/256dpi/xo"
"github.com/stretchr/testify/assert"

"github.com/256dpi/fire"
"github.com/256dpi/fire/axe"
Expand Down Expand Up @@ -63,3 +64,33 @@ func withTester(t *testing.T, fn func(*testing.T, *fire.Tester)) {
fn(t, tester)
})
}

func TestParseContentDisposition(t *testing.T) {
typ, params, err := parseContentDisposition("foo")
assert.NoError(t, err)
assert.Equal(t, "foo", typ)
assert.Empty(t, params)

_, _, err = parseContentDisposition("foo; bar")
assert.Error(t, err)

typ, params, err = parseContentDisposition("foo; bar=baz")
assert.NoError(t, err)
assert.Equal(t, "foo", typ)
assert.Equal(t, map[string]string{"bar": "baz"}, params)

typ, params, err = parseContentDisposition("foo; bar*=utf-8''baz")
assert.NoError(t, err)
assert.Equal(t, "foo", typ)
assert.Equal(t, map[string]string{"bar": "baz"}, params)

typ, params, err = parseContentDisposition("foo; bar*=utf-8''A%20B")
assert.NoError(t, err)
assert.Equal(t, "foo", typ)
assert.Equal(t, map[string]string{"bar": "A B"}, params)

typ, params, err = parseContentDisposition("foo; bar*=utf-8''file_$!&()+#@,-_.mp4")
assert.NoError(t, err)
assert.Equal(t, "foo", typ)
assert.Equal(t, map[string]string{"bar": "file_$!&()+#@,-_.mp4"}, params)
}

0 comments on commit 79816c3

Please sign in to comment.