diff --git a/storage/bucket.go b/storage/bucket.go index 5f1fdaaa..3b6bdf19 100644 --- a/storage/bucket.go +++ b/storage/bucket.go @@ -725,7 +725,7 @@ func EncodedEntryWithoutKey(bucket string) string { return base64.URLEncoding.EncodeToString([]byte(bucket)) } -// MakePublicURL 用来生成公开空间资源下载链接 +// MakePublicURL 用来生成公开空间资源下载链接,注意该方法并不会对 key 进行 escape func MakePublicURL(domain, key string) (finalUrl string) { domain = strings.TrimRight(domain, "/") srcUrl := fmt.Sprintf("%s/%s", domain, key) @@ -734,7 +734,36 @@ func MakePublicURL(domain, key string) (finalUrl string) { return } -// MakePrivateURL 用来生成私有空间资源下载链接 +// MakePublicURLv2 用来生成公开空间资源下载链接,并且该方法确保 key 将会被 escape +func MakePublicURLv2(domain, key string) string { + return MakePublicURLv2WithQuery(domain, key, nil) +} + +// MakePublicURLv2WithQuery 用来生成公开空间资源下载链接,并且该方法确保 key 将会被 escape,并在 URL 后追加经过编码的查询参数 +func MakePublicURLv2WithQuery(domain, key string, query url.Values) string { + var rawQuery string + if query != nil { + rawQuery = query.Encode() + } + return makePublicURLv2WithRawQuery(domain, key, rawQuery) +} + +// MakePublicURLv2WithQueryString 用来生成公开空间资源下载链接,并且该方法确保 key 将会被 escape,并在 URL 后直接追加查询参数 +func makePublicURLv2WithQueryString(domain, key, query string) string { + return makePublicURLv2WithRawQuery(domain, key, urlEncodeQuery(query)) +} + +func makePublicURLv2WithRawQuery(domain, key, rawQuery string) string { + domain = strings.TrimRight(domain, "/") + srcUrl := fmt.Sprintf("%s/%s", domain, urlEncodeQuery(key)) + if rawQuery != "" { + srcUrl += "?" + rawQuery + } + srcUri, _ := url.Parse(srcUrl) + return srcUri.String() +} + +// MakePrivateURL 用来生成私有空间资源下载链接,注意该方法并不会对 key 进行 escape func MakePrivateURL(mac *auth.Credentials, domain, key string, deadline int64) (privateURL string) { publicURL := MakePublicURL(domain, key) urlToSign := publicURL @@ -748,6 +777,46 @@ func MakePrivateURL(mac *auth.Credentials, domain, key string, deadline int64) ( return } +// MakePrivateURLv2 用来生成私有空间资源下载链接,并且该方法确保 key 将会被 escape +func MakePrivateURLv2(mac *auth.Credentials, domain, key string, deadline int64) (privateURL string) { + return MakePrivateURLv2WithQuery(mac, domain, key, nil, deadline) +} + +// MakePrivateURLv2WithQuery 用来生成私有空间资源下载链接,并且该方法确保 key 将会被 escape,并在 URL 后追加经过编码的查询参数 +func MakePrivateURLv2WithQuery(mac *auth.Credentials, domain, key string, query url.Values, deadline int64) (privateURL string) { + var rawQuery string + if query != nil { + rawQuery = query.Encode() + } + return makePrivateURLv2WithRawQuery(mac, domain, key, rawQuery, deadline) +} + +// MakePrivateURLv2WithQueryString 用来生成私有空间资源下载链接,并且该方法确保 key 将会被 escape,并在 URL 后直接追加查询参数 +func MakePrivateURLv2WithQueryString(mac *auth.Credentials, domain, key, query string, deadline int64) (privateURL string) { + return makePrivateURLv2WithRawQuery(mac, domain, key, urlEncodeQuery(query), deadline) +} + +func makePrivateURLv2WithRawQuery(mac *auth.Credentials, domain, key, rawQuery string, deadline int64) (privateURL string) { + publicURL := makePublicURLv2WithRawQuery(domain, key, rawQuery) + urlToSign := publicURL + if strings.Contains(publicURL, "?") { + urlToSign = fmt.Sprintf("%s&e=%d", urlToSign, deadline) + } else { + urlToSign = fmt.Sprintf("%s?e=%d", urlToSign, deadline) + } + token := mac.Sign([]byte(urlToSign)) + privateURL = fmt.Sprintf("%s&token=%s", urlToSign, token) + return +} + +func urlEncodeQuery(str string) (ret string) { + str = url.QueryEscape(str) + str = strings.Replace(str, "%2F", "/", -1) + str = strings.Replace(str, "%7C", "|", -1) + str = strings.Replace(str, "+", "%20", -1) + return str +} + type listFilesRet2 struct { Marker string `json:"marker"` Item ListItem `json:"item"` diff --git a/storage/bucket_test.go b/storage/bucket_test.go index 0765f7b0..614ebdce 100644 --- a/storage/bucket_test.go +++ b/storage/bucket_test.go @@ -4,6 +4,7 @@ import ( "fmt" "math/rand" "net/http" + "net/url" "os" "strings" "testing" @@ -583,3 +584,55 @@ func TestSetBucketAccessMode(t *testing.T) { t.Fatalf("TestSetBucketAccessMode: %q\n", err) } } + +func TestMakeURL(t *testing.T) { + keys := map[string]string{ //rawKey => encodeKey, + "": "", + "abc_def.mp4": "abc_def.mp4", + "/ab/cd": "/ab/cd", + "ab/中文/de": "ab/%E4%B8%AD%E6%96%87/de", + // "ab+-*de f": "ab%2B-%2Ade%20f", + "ab:cd": "ab%3Acd", + // "ab@cd": "ab%40cd", + "ab?cd=ef": "ab%3Fcd%3Def", + "ab#e~f": "ab%23e~f", + "ab//cd": "ab//cd", + "abc%2F%2B": "abc%252F%252B", + "ab cd": "ab%20cd", + // "ab/c:d?e#f//gh汉子": "ab/c%3Ad%3Fe%23f//gh%E6%B1%89%E5%AD%90", + } + s := MakePublicURL("https://abc.com:123/", "123/def?@#") + if s != "https://abc.com:123/123/def?@" { + t.Fatalf("TestMakeURL: %q\n", s) + } + + s = MakePublicURL("abc.com:123/", "123/def?@#") + if s != "abc.com:123/123/def?@" { + t.Fatalf("TestMakeURL: %q\n", s) + } + + q := make(url.Values) + q.Add("?", "#") + s = MakePublicURLv2WithQuery("https://abc.com:123/", "123/def?@#", q) + if s != "https://abc.com:123/123/def%3F%40%23?%3F=%23" { + t.Fatalf("TestMakeURL: %q\n", s) + } + + s = makePublicURLv2WithQueryString("http://abc.com:123/", "123/def?@#|", "123/def?@#|") + if s != "http://abc.com:123/123/def%3F@%23%7C?123/def%3F%40%23|" { + t.Fatalf("TestMakeURL: %q\n", s) + } + + s = MakePublicURLv2("http://abc.com:123/", "123/def?@#") + if s != "http://abc.com:123/123/def%3F%40%23" { + t.Fatalf("TestMakeURL: %q\n", s) + } + + for rawKey, encodedKey := range keys { + s = MakePublicURLv2("http://abc.com:123/", rawKey) + e := "http://abc.com:123/" + encodedKey + if s != e { + t.Fatalf("TestMakeURL: %q %q\n", s, e) + } + } +}