Browse Source

Fix treating percent-encoded symbols in `s3://`, `gcs://`, `abs://`, and `swift://` URLs

DarthSim 1 year ago
parent
commit
c01158977c

+ 2 - 1
CHANGELOG.md

@@ -9,7 +9,7 @@
 
 ### Changed
 - Automatically add `http://` scheme to the `IMGPROXY_S3_ENDPOINT` value if it has no scheme.
-- Trim redundant slashes in the S3 object key.
+- Trim redundant slashes in the S3, GCS, ABS, and Swift object keys.
 - Rename `IMGPROXY_WRITE_TIMEOUT` to `IMGPROXY_TIMEOUT`. The old name is deprecated but still supported.
 - Rename `IMGPROXY_READ_TIMEOUT` to `IMGPROXY_READ_REQUEST_TIMEOUT`. The old name is deprecated but still supported.
 - (pro) Allow specifying [gradient](https://docs.imgproxy.net/latest/usage/processing#gradient) direction as an angle in degrees.
@@ -17,6 +17,7 @@
 ### Fix
 - Fix HEIC/AVIF dimension limit handling.
 - Fix SVG detection when the root element has a namespace.
+- Fix treating percent-encoded symbols in `s3://`, `gcs://`, `abs://`, and `swift://` URLs.
 - (pro) Fix style injection to SVG.
 - (pro) Fix video tiles generation when the video's SAR is not `1`.
 

+ 2 - 2
transport/azure/azure.go

@@ -19,6 +19,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/httprange"
 	defaultTransport "github.com/imgproxy/imgproxy/v3/transport"
+	"github.com/imgproxy/imgproxy/v3/transport/common"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -83,8 +84,7 @@ func New() (http.RoundTripper, error) {
 }
 
 func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
-	container := req.URL.Host
-	key := strings.TrimPrefix(req.URL.Path, "/")
+	container, key := common.GetBucketAndKey(req.URL)
 
 	if len(container) == 0 || len(key) == 0 {
 		body := strings.NewReader("Invalid ABS URL: container name or object key is empty")

+ 25 - 0
transport/common/common.go

@@ -0,0 +1,25 @@
+package common
+
+import (
+	"net/url"
+	"strings"
+)
+
+func GetBucketAndKey(u *url.URL) (bucket, key string) {
+	bucket = u.Host
+
+	// We can't use u.Path here because `url.Parse` unescapes the original URL's path.
+	// So we have to use `u.RawPath` if it's available.
+	// If it is not available, then `u.EscapedPath()` is the same as the original URL's path
+	// before `url.Parse`.
+	// See: https://cs.opensource.google/go/go/+/refs/tags/go1.22.4:src/net/url/url.go;l=680
+	if len(u.RawPath) > 0 {
+		key = u.RawPath
+	} else {
+		key = u.EscapedPath()
+	}
+
+	key = strings.TrimLeft(key, "/")
+
+	return
+}

+ 8 - 4
transport/fs/fs.go

@@ -15,6 +15,7 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/httprange"
+	"github.com/imgproxy/imgproxy/v3/transport/common"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -29,10 +30,13 @@ func New() transport {
 func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
 	header := make(http.Header)
 
-	f, err := t.fs.Open(req.URL.Path)
+	_, path := common.GetBucketAndKey(req.URL)
+	path = "/" + path
+
+	f, err := t.fs.Open(path)
 	if err != nil {
 		if os.IsNotExist(err) {
-			return respNotFound(req, fmt.Sprintf("%s doesn't exist", req.URL.Path)), nil
+			return respNotFound(req, fmt.Sprintf("%s doesn't exist", path)), nil
 		}
 		return nil, err
 	}
@@ -43,7 +47,7 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
 	}
 
 	if fi.IsDir() {
-		return respNotFound(req, fmt.Sprintf("%s is directory", req.URL.Path)), nil
+		return respNotFound(req, fmt.Sprintf("%s is directory", path)), nil
 	}
 
 	statusCode := 200
@@ -75,7 +79,7 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
 
 	default:
 		if config.ETagEnabled {
-			etag := BuildEtag(req.URL.Path, fi)
+			etag := BuildEtag(path, fi)
 			header.Set("ETag", etag)
 		}
 

+ 2 - 2
transport/gcs/gcs.go

@@ -17,6 +17,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/httprange"
 	defaultTransport "github.com/imgproxy/imgproxy/v3/transport"
+	"github.com/imgproxy/imgproxy/v3/transport/common"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -77,8 +78,7 @@ func New() (http.RoundTripper, error) {
 }
 
 func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
-	bucket := req.URL.Host
-	key := strings.TrimPrefix(req.URL.Path, "/")
+	bucket, key := common.GetBucketAndKey(req.URL)
 
 	if len(bucket) == 0 || len(key) == 0 {
 		body := strings.NewReader("Invalid GCS URL: bucket name or object key is empty")

+ 2 - 2
transport/s3/s3.go

@@ -24,6 +24,7 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	defaultTransport "github.com/imgproxy/imgproxy/v3/transport"
+	"github.com/imgproxy/imgproxy/v3/transport/common"
 )
 
 type s3Client interface {
@@ -102,8 +103,7 @@ func New() (http.RoundTripper, error) {
 }
 
 func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
-	bucket := req.URL.Host
-	key := strings.TrimLeft(req.URL.Path, "/")
+	bucket, key := common.GetBucketAndKey(req.URL)
 
 	if len(bucket) == 0 || len(key) == 0 {
 		body := strings.NewReader("Invalid S3 URL: bucket name or object key is empty")

+ 2 - 3
transport/swift/swift.go

@@ -13,6 +13,7 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	defaultTransport "github.com/imgproxy/imgproxy/v3/transport"
+	"github.com/imgproxy/imgproxy/v3/transport/common"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -50,9 +51,7 @@ func New() (http.RoundTripper, error) {
 }
 
 func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
-	// Users should have converted the object storage URL in the format of swift://{container}/{object}
-	container := req.URL.Host
-	objectName := strings.TrimPrefix(req.URL.Path, "/")
+	container, objectName := common.GetBucketAndKey(req.URL)
 
 	if len(container) == 0 || len(objectName) == 0 {
 		body := strings.NewReader("Invalid Swift URL: container name or object name is empty")