diff --git a/lib/rest/url.go b/lib/rest/url.go index 8f443d59d..fcda35693 100644 --- a/lib/rest/url.go +++ b/lib/rest/url.go @@ -3,6 +3,7 @@ package rest import ( "fmt" "net/url" + "strings" ) // URLJoin joins a URL and a path returning a new URL @@ -24,3 +25,24 @@ func URLPathEscape(in string) string { u.Path = in return u.String() } + +// URLPathEscapeAll escapes URL path the in string using URL escaping rules +// +// It escapes every character except [A-Za-z0-9] and / +func URLPathEscapeAll(in string) string { + var b strings.Builder + b.Grow(len(in) * 3) // worst case: every byte escaped + const hex = "0123456789ABCDEF" + for i := range len(in) { + c := in[i] + if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '/' { + b.WriteByte(c) + } else { + b.WriteByte('%') + b.WriteByte(hex[c>>4]) + b.WriteByte(hex[c&0x0F]) + } + } + return b.String() +} diff --git a/lib/rest/url_test.go b/lib/rest/url_test.go index 3f481dfd9..263634879 100644 --- a/lib/rest/url_test.go +++ b/lib/rest/url_test.go @@ -59,3 +59,27 @@ func TestURLPathEscape(t *testing.T) { assert.Equal(t, test.want, got, fmt.Sprintf("Test %d path = %q", i, test.path)) } } + +func TestURLPathEscapeAll(t *testing.T) { + tests := []struct { + in string + want string + }{ + {"", ""}, + {"/hello.txt", "/hello%2Etxt"}, + {"With Space", "With%20Space"}, + {"With Colon:", "With%20Colon%3A"}, + {"With Percent%", "With%20Percent%25"}, + {"abc/XYZ123", "abc/XYZ123"}, + {"hello world", "hello%20world"}, + {"$test", "%24test"}, + {"ümlaut", "%C3%BCmlaut"}, + {"", ""}, + {" /?", "%20/%3F"}, + } + + for _, test := range tests { + got := URLPathEscapeAll(test.in) + assert.Equal(t, test.want, got) + } +}