mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-09-05 15:46:37 +08:00
fix: correct S3 signature and path handling (#213)
* fix: correct S3 signature and path handling * fix: preserve existing overwrite behavior
This commit is contained in:
+68
-6
@@ -133,7 +133,7 @@ func (c *Client) Put(ctx context.Context, key string, r io.Reader, size int64) e
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode >= 300 {
|
if resp.StatusCode >= 300 {
|
||||||
return fmt.Errorf("put object failed: %s", resp.Status)
|
return responseError("put object", resp)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -170,10 +170,21 @@ func signRequest(req *http.Request, region, accessKey, secretKey string, payload
|
|||||||
req.Header.Set("x-amz-date", amzDate)
|
req.Header.Set("x-amz-date", amzDate)
|
||||||
req.Header.Set("x-amz-content-sha256", payloadHash)
|
req.Header.Set("x-amz-content-sha256", payloadHash)
|
||||||
|
|
||||||
// Canonical headers
|
// Canonical headers. Host is required by SigV4, but Go stores it on
|
||||||
var headers []string
|
// Request.Host/URL.Host rather than in Request.Header.
|
||||||
|
headerValues := map[string]string{
|
||||||
|
"host": req.URL.Host,
|
||||||
|
}
|
||||||
|
if req.Host != "" {
|
||||||
|
headerValues["host"] = req.Host
|
||||||
|
}
|
||||||
for k := range req.Header {
|
for k := range req.Header {
|
||||||
headers = append(headers, strings.ToLower(k))
|
headerValues[strings.ToLower(k)] = strings.TrimSpace(req.Header.Get(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers []string
|
||||||
|
for k := range headerValues {
|
||||||
|
headers = append(headers, k)
|
||||||
}
|
}
|
||||||
sort.Strings(headers)
|
sort.Strings(headers)
|
||||||
|
|
||||||
@@ -181,7 +192,7 @@ func signRequest(req *http.Request, region, accessKey, secretKey string, payload
|
|||||||
for _, k := range headers {
|
for _, k := range headers {
|
||||||
canonicalHeaders.WriteString(k)
|
canonicalHeaders.WriteString(k)
|
||||||
canonicalHeaders.WriteString(":")
|
canonicalHeaders.WriteString(":")
|
||||||
canonicalHeaders.WriteString(strings.TrimSpace(req.Header.Get(k)))
|
canonicalHeaders.WriteString(headerValues[k])
|
||||||
canonicalHeaders.WriteString("\n")
|
canonicalHeaders.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,7 +200,7 @@ func signRequest(req *http.Request, region, accessKey, secretKey string, payload
|
|||||||
|
|
||||||
canonicalRequest := strings.Join([]string{
|
canonicalRequest := strings.Join([]string{
|
||||||
req.Method,
|
req.Method,
|
||||||
req.URL.EscapedPath(),
|
canonicalURI(req.URL.Path),
|
||||||
req.URL.RawQuery,
|
req.URL.RawQuery,
|
||||||
canonicalHeaders.String(),
|
canonicalHeaders.String(),
|
||||||
signedHeaders,
|
signedHeaders,
|
||||||
@@ -219,3 +230,54 @@ func signRequest(req *http.Request, region, accessKey, secretKey string, payload
|
|||||||
req.Header.Set("Authorization", auth)
|
req.Header.Set("Authorization", auth)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func responseError(operation string, resp *http.Response) error {
|
||||||
|
body, err := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s failed: %s", operation, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
message := strings.TrimSpace(string(body))
|
||||||
|
if message == "" {
|
||||||
|
return fmt.Errorf("%s failed: %s", operation, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("%s failed: %s: %s", operation, resp.Status, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalURI(path string) string {
|
||||||
|
if path == "" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
c := path[i]
|
||||||
|
if shouldEscapePathByte(c) {
|
||||||
|
b.WriteByte('%')
|
||||||
|
b.WriteByte("0123456789ABCDEF"[c>>4])
|
||||||
|
b.WriteByte("0123456789ABCDEF"[c&15])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.WriteByte(c)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldEscapePathByte(c byte) bool {
|
||||||
|
if c >= 'A' && c <= 'Z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c >= 'a' && c <= 'z' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '-', '.', '_', '~', '/':
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user