Files
zhanglq 1d8c6e314b fix: use path.Clean instead of filepath.Clean for cross-platform URI canonicalization
filepath.Clean converts forward slashes to backslashes on Windows,
which breaks the AWS V4 signature calculation by corrupting the
canonical URI. Use path.Clean which always uses forward slashes,
matching the S3 API expectation.
2026-07-02 14:55:57 +08:00

152 lines
3.0 KiB
Go

// LICENSE BSD-2-Clause-FreeBSD
// Copyright (c) 2018, Rohan Verma <hello@rohanverma.net>
// Copyright (C) 2012 Blake Mizerany
// contains code from: github.com/bmizerany/aws4
package simples3
import (
"bytes"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
p "path"
"sort"
"strings"
"time"
)
func (s3 *S3) signKeys(t time.Time) []byte {
h := makeHMac([]byte("AWS4"+s3.SecretKey), []byte(t.Format(shortTimeFormat)))
h = makeHMac(h, []byte(s3.Region))
h = makeHMac(h, []byte(serviceName))
h = makeHMac(h, []byte("aws4_request"))
return h
}
func (s3 *S3) writeRequest(w io.Writer, r *http.Request) {
r.Header.Set("host", r.Host)
w.Write([]byte(r.Method))
w.Write(newLine)
writeURI(w, r)
w.Write(newLine)
writeQuery(w, r)
w.Write(newLine)
writeHeader(w, r)
w.Write(newLine)
w.Write(newLine)
writeHeaderList(w, r)
w.Write(newLine)
writeBody(w, r)
}
func (s3 *S3) writeStringToSign(w io.Writer, t time.Time, r *http.Request) {
w.Write([]byte(algorithm))
w.Write(newLine)
w.Write([]byte(t.Format(amzDateISO8601TimeFormat)))
w.Write(newLine)
w.Write([]byte(s3.creds(t)))
w.Write(newLine)
h := sha256.New()
s3.writeRequest(h, r)
fmt.Fprintf(w, "%x", h.Sum(nil))
}
func (s3 *S3) creds(t time.Time) string {
return t.Format(shortTimeFormat) + "/" + s3.Region + "/" + serviceName + "/aws4_request"
}
func writeURI(w io.Writer, r *http.Request) {
path := r.URL.RequestURI()
if r.URL.RawQuery != "" {
path = path[:len(path)-len(r.URL.RawQuery)-1]
}
slash := strings.HasSuffix(path, "/")
path = p.Clean(path)
if path != "/" && slash {
path += "/"
}
w.Write([]byte(path))
}
func writeQuery(w io.Writer, r *http.Request) {
var a []string
for k, vs := range r.URL.Query() {
k = url.QueryEscape(k)
for _, v := range vs {
if v == "" {
a = append(a, k+"=")
} else {
v = url.QueryEscape(v)
a = append(a, k+"="+v)
}
}
}
sort.Strings(a)
for i, s := range a {
if i > 0 {
w.Write([]byte{'&'})
}
w.Write([]byte(s))
}
}
func writeHeader(w io.Writer, r *http.Request) {
i, a := 0, make([]string, len(r.Header))
for k, v := range r.Header {
sort.Strings(v)
a[i] = strings.ToLower(k) + ":" + strings.Join(v, ",")
i++
}
sort.Strings(a)
for i, s := range a {
if i > 0 {
w.Write(newLine)
}
io.WriteString(w, s)
}
}
func writeHeaderList(w io.Writer, r *http.Request) {
i, a := 0, make([]string, len(r.Header))
for k := range r.Header {
a[i] = strings.ToLower(k)
i++
}
sort.Strings(a)
for i, s := range a {
if i > 0 {
w.Write([]byte{';'})
}
w.Write([]byte(s))
}
}
func writeBody(w io.Writer, r *http.Request) {
var (
b []byte
err error
)
// If the payload is empty, use the empty string as the input to the SHA256 function
// http://docs.amazonwebservices.com/general/latest/gr/sigv4-create-canonical-request.html
if r.Body == nil {
b = []byte("")
} else {
b, err = ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(b))
}
h := sha256.New()
h.Write(b)
fmt.Fprintf(w, "%x", h.Sum(nil))
}