Fix issue presigning request with custom headers, also fix small deviation from spec related to lower case headers (#35)

Fix presigned URLs with custom headers (e.g., Content-Length) by:

1. Always URI-encoding query parameter values (removes incorrect special case for X-Amz-SignedHeaders)
2. Lowercasing header names per AWS Signature V4 spec

Fixes SignatureDoesNotMatch errors with Cloudflare R2 and other strict S3-compatible services.

PR: #35
Author: @Napam
This commit is contained in:
Naphat Amundsen
2026-01-23 12:29:56 +05:30
committed by GitHub
parent f02c375b6d
commit 57fa0ece6f
2 changed files with 100 additions and 29 deletions
+7 -24
View File
@@ -80,7 +80,8 @@ func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
// Add host to Headers
signedHeaders := map[string][]byte{}
for k, v := range in.ExtraHeaders {
signedHeaders[k] = []byte(v)
// AWS requires header names to be lowercase per spec
signedHeaders[strings.ToLower(k)] = []byte(v)
}
signedHeaders["host"] = []byte(hostname)
@@ -142,12 +143,7 @@ func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
for i, k := range sortedQS {
h.Write([]byte(awsURIEncode(k)))
h.Write([]byte{'='})
// X-Amz-SignedHeaders already has properly formatted semicolons, retain as is.
if k == HdrXAmzSignedHeaders {
h.Write([]byte(queryString[k]))
} else {
h.Write([]byte(awsURIEncode(queryString[k])))
}
h.Write([]byte(awsURIEncode(queryString[k])))
if i < len(sortedQS)-1 {
h.Write([]byte{'&'})
}
@@ -225,12 +221,7 @@ func (s3 *S3) GeneratePresignedURL(in PresignedInput) string {
for i, k := range sortedQS {
b.WriteString(awsURIEncode(k))
b.WriteRune('=')
// X-Amz-SignedHeaders already has properly formatted semicolons
if k == HdrXAmzSignedHeaders {
b.WriteString(queryString[k])
} else {
b.WriteString(awsURIEncode(queryString[k]))
}
b.WriteString(awsURIEncode(queryString[k]))
if i < len(sortedQS)-1 {
b.WriteRune('&')
}
@@ -293,6 +284,7 @@ func (s3 *S3) GeneratePresignedUploadPartURL(in PresignedMultipartInput) string
}
// Add host to Headers
// AWS requires header names to be lowercase per spec
signedHeaders := map[string][]byte{
"host": []byte(hostname),
}
@@ -351,11 +343,7 @@ func (s3 *S3) GeneratePresignedUploadPartURL(in PresignedMultipartInput) string
h.Write([]byte(awsURIEncode(k)))
h.Write([]byte{'='})
// X-Amz-SignedHeaders already has properly formatted semicolons, retain as is.
if k == HdrXAmzSignedHeaders {
h.Write([]byte(queryString[k]))
} else {
h.Write([]byte(awsURIEncode(queryString[k])))
}
h.Write([]byte(awsURIEncode(queryString[k])))
if i < len(sortedQS)-1 {
h.Write([]byte{'&'})
}
@@ -424,12 +412,7 @@ func (s3 *S3) GeneratePresignedUploadPartURL(in PresignedMultipartInput) string
for i, k := range sortedQS {
b.WriteString(awsURIEncode(k))
b.WriteRune('=')
// X-Amz-SignedHeaders already has properly formatted semicolons
if k == HdrXAmzSignedHeaders {
b.WriteString(queryString[k])
} else {
b.WriteString(awsURIEncode(queryString[k]))
}
b.WriteString(awsURIEncode(queryString[k]))
if i < len(sortedQS)-1 {
b.WriteRune('&')
}