From 291c42801633b707052fca1977c3e1cf38bba0c6 Mon Sep 17 00:00:00 2001 From: Matt Joiner Date: Fri, 23 Dec 2022 11:18:36 +1100 Subject: [PATCH] Change default webseed path escaping to work for all S3-compatible providers --- webseed/request.go | 7 +++- webseed/request_test.go | 78 ++++++++++------------------------------- 2 files changed, 24 insertions(+), 61 deletions(-) diff --git a/webseed/request.go b/webseed/request.go index a38e6372..e7c28155 100644 --- a/webseed/request.go +++ b/webseed/request.go @@ -24,7 +24,12 @@ func EscapePath(pathComps []string) string { func defaultPathEscaper(pathComps []string) string { var ret []string for _, comp := range pathComps { - ret = append(ret, url.QueryEscape(comp)) + esc := url.PathEscape(comp) + // S3 incorrectly escapes + in paths to spaces, so we add an extra encoding for that. This + // seems to be handled correctly regardless of whether an endpoint uses query or path + // escaping. + esc = strings.ReplaceAll(esc, "+", "%2B") + ret = append(ret, esc) } return path.Join(ret...) } diff --git a/webseed/request_test.go b/webseed/request_test.go index 7f691e0a..6fd033bb 100644 --- a/webseed/request_test.go +++ b/webseed/request_test.go @@ -2,71 +2,29 @@ package webseed import ( "net/url" - "path" "testing" qt "github.com/frankban/quicktest" ) -func TestEscapePath(t *testing.T) { +func TestDefaultPathEscaper(t *testing.T) { c := qt.New(t) - test := func( - parts []string, result string, - escaper PathEscaper, - unescaper func(string) (string, error), - ) { - unescaped, err := unescaper(escaper(parts)) - if !c.Check(err, qt.IsNil) { - return - } - c.Check(unescaped, qt.Equals, result) + test := func(unescaped string, parts ...string) { + escaped := defaultPathEscaper(parts) + pathUnescaped, err := url.PathUnescape(escaped) + c.Assert(err, qt.IsNil) + c.Assert(pathUnescaped, qt.Equals, unescaped) + queryUnescaped, err := url.QueryUnescape(escaped) + c.Assert(err, qt.IsNil) + c.Assert(queryUnescaped, qt.Equals, unescaped) } - - // Test with nil escapers (always uses url.QueryEscape) - // ------ - test( - []string{"a_b-c", "d + e.f"}, - "a_b-c/d + e.f", - defaultPathEscaper, - url.QueryUnescape, - ) - test( - []string{"a_1-b_c2", "d 3. (e, f).g"}, - "a_1-b_c2/d 3. (e, f).g", - defaultPathEscaper, - url.QueryUnescape, - ) - - // Test with custom escapers - // ------ - test( - []string{"a_b-c", "d + e.f"}, - "a_b-c/d + e.f", - func(s []string) string { - var ret []string - for _, comp := range s { - ret = append(ret, url.PathEscape(comp)) - } - return path.Join(ret...) - }, - url.PathUnescape, - ) - test( - []string{"a_1-b_c2", "d 3. (e, f).g"}, - "a_1-b_c2/d 3. (e, f).g", - func(s []string) string { - var ret []string - for _, comp := range s { - ret = append(ret, url.PathEscape(comp)) - } - return path.Join(ret...) - }, - url.PathUnescape, - ) -} - -func TestEscapePathForEmptyInfoName(t *testing.T) { - qt.Check(t, defaultPathEscaper([]string{`ノ┬─┬ノ ︵ ( \o°o)\`}), qt.Equals, "%E3%83%8E%E2%94%AC%E2%94%80%E2%94%AC%E3%83%8E+%EF%B8%B5+%28+%5Co%C2%B0o%29%5C") - qt.Check(t, defaultPathEscaper([]string{"hello", "world"}), qt.Equals, "hello/world") - qt.Check(t, defaultPathEscaper([]string{"war", "and", "peace"}), qt.Equals, "war/and/peace") + test("a_b-c/d + e.f", "a_b-c", "d + e.f") + test("a_1-b_c2/d 3. (e, f).g", "a_1-b_c2", "d 3. (e, f).g") + test("a_b-c/d + e.f", "a_b-c", "d + e.f") + test("a_1-b_c2/d 3. (e, f).g", "a_1-b_c2", "d 3. (e, f).g") + test("war/and/peace", "war", "and", "peace") + test("hello/world", "hello", "world") + test(`ノ┬─┬ノ ︵ ( \o°o)\`, `ノ┬─┬ノ ︵ ( \o°o)\`) + test(`%aa + %bb/Parsi Tv - سرقت و باز کردن در ماشین در کم‌تر از ۳ ثانیه + فیلم.webm`, + `%aa + %bb`, `Parsi Tv - سرقت و باز کردن در ماشین در کم‌تر از ۳ ثانیه + فیلم.webm`) } -- 2.44.0