Merge pull request 'fix: encode source query in GenerateSignedURL to avoid malformed URLs (closes #2)' (#7) from fix/issue-2 into main
Reviewed-on: #7
This commit was merged in pull request #7.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -96,22 +97,24 @@ func (s *Signer) GenerateSignedURL(req *ImageRequest, ttl time.Duration) (path s
|
|||||||
sizeStr = fmt.Sprintf("%dx%d", req.Size.Width, req.Size.Height)
|
sizeStr = fmt.Sprintf("%dx%d", req.Size.Width, req.Size.Height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the path
|
// Build the path.
|
||||||
path = fmt.Sprintf("/v1/image/%s%s/%s.%s",
|
// When a source query is present, it is embedded as a path segment
|
||||||
|
// (e.g. /host/path?query/size.fmt) so that ParseImagePath can extract
|
||||||
|
// it from the last-slash split. The "?" inside a path segment is
|
||||||
|
// percent-encoded by clients but chi delivers it decoded, which is
|
||||||
|
// exactly what the URL parser expects.
|
||||||
|
if req.SourceQuery != "" {
|
||||||
|
path = fmt.Sprintf("/v1/image/%s%s%%3F%s/%s.%s",
|
||||||
req.SourceHost,
|
req.SourceHost,
|
||||||
req.SourcePath,
|
req.SourcePath,
|
||||||
|
url.PathEscape(req.SourceQuery),
|
||||||
sizeStr,
|
sizeStr,
|
||||||
req.Format,
|
req.Format,
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
// Append query if present
|
path = fmt.Sprintf("/v1/image/%s%s/%s.%s",
|
||||||
if req.SourceQuery != "" {
|
|
||||||
// The query goes before the size segment in our URL scheme
|
|
||||||
// So we need to rebuild the path
|
|
||||||
path = fmt.Sprintf("/v1/image/%s%s?%s/%s.%s",
|
|
||||||
req.SourceHost,
|
req.SourceHost,
|
||||||
req.SourcePath,
|
req.SourcePath,
|
||||||
req.SourceQuery,
|
|
||||||
sizeStr,
|
sizeStr,
|
||||||
req.Format,
|
req.Format,
|
||||||
)
|
)
|
||||||
|
|||||||
55
internal/imgcache/signature_query_test.go
Normal file
55
internal/imgcache/signature_query_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package imgcache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerateSignedURL_WithQueryString(t *testing.T) {
|
||||||
|
signer := NewSigner("test-secret-key-for-testing!")
|
||||||
|
|
||||||
|
req := &ImageRequest{
|
||||||
|
SourceHost: "cdn.example.com",
|
||||||
|
SourcePath: "/photos/cat.jpg",
|
||||||
|
SourceQuery: "token=abc&v=2",
|
||||||
|
Size: Size{Width: 800, Height: 600},
|
||||||
|
Format: FormatWebP,
|
||||||
|
}
|
||||||
|
|
||||||
|
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
|
||||||
|
|
||||||
|
// The path must NOT contain a bare "?" that would be interpreted as a query string delimiter.
|
||||||
|
// The size segment must appear as the last path component.
|
||||||
|
if strings.Contains(path, "?token=abc") {
|
||||||
|
t.Errorf("GenerateSignedURL() produced bare query string in path: %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The size segment must be present in the path
|
||||||
|
if !strings.Contains(path, "/800x600.webp") {
|
||||||
|
t.Errorf("GenerateSignedURL() missing size segment in path: %q", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path should end with the size.format, not with query params
|
||||||
|
if !strings.HasSuffix(path, "/800x600.webp") {
|
||||||
|
t.Errorf("GenerateSignedURL() path should end with size.format: %q", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateSignedURL_WithoutQueryString(t *testing.T) {
|
||||||
|
signer := NewSigner("test-secret-key-for-testing!")
|
||||||
|
|
||||||
|
req := &ImageRequest{
|
||||||
|
SourceHost: "cdn.example.com",
|
||||||
|
SourcePath: "/photos/cat.jpg",
|
||||||
|
Size: Size{Width: 800, Height: 600},
|
||||||
|
Format: FormatWebP,
|
||||||
|
}
|
||||||
|
|
||||||
|
path, _, _ := signer.GenerateSignedURL(req, time.Hour)
|
||||||
|
|
||||||
|
expected := "/v1/image/cdn.example.com/photos/cat.jpg/800x600.webp"
|
||||||
|
if path != expected {
|
||||||
|
t.Errorf("GenerateSignedURL() path = %q, want %q", path, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user