1
0
mirror of https://github.com/rclone/rclone.git synced 2025-10-06 05:47:10 +02:00

lib/rest: add URLPathEscapeAll to URL escape as many chars as possible

This commit is contained in:
Nick Craig-Wood
2025-09-02 16:13:28 +01:00
parent 4a7e62b79c
commit 1d91618d9e
2 changed files with 46 additions and 0 deletions

View File

@@ -3,6 +3,7 @@ package rest
import ( import (
"fmt" "fmt"
"net/url" "net/url"
"strings"
) )
// URLJoin joins a URL and a path returning a new URL // URLJoin joins a URL and a path returning a new URL
@@ -24,3 +25,24 @@ func URLPathEscape(in string) string {
u.Path = in u.Path = in
return u.String() 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()
}

View File

@@ -59,3 +59,27 @@ func TestURLPathEscape(t *testing.T) {
assert.Equal(t, test.want, got, fmt.Sprintf("Test %d path = %q", i, test.path)) 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)
}
}