Explorar el Código

Fix handing 206 responses from source with full Content-Range

DarthSim hace 2 meses
padre
commit
f100d3910b
Se han modificado 2 ficheros con 47 adiciones y 3 borrados
  1. 3 0
      CHANGELOG.md
  2. 44 3
      imagedata/download.go

+ 3 - 0
CHANGELOG.md

@@ -4,6 +4,9 @@
 ### Added
 - Add [IMGPROXY_BASE64_URL_INCLUDES_FILENAME](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_BASE64_URL_INCLUDES_FILENAME) config.
 
+### Changed
+- Treat 206 (Partial Content) responses from a source server as 200 (OK) when they contain a full content range.
+
 ## [3.27.2] - 2025-01-27
 ### Fixed
 - Fix preventing requests to `0.0.0.0` when imgproxy is configured to deny loopback addresses.

+ 44 - 3
imagedata/download.go

@@ -7,6 +7,8 @@ import (
 	"io"
 	"net/http"
 	"net/http/cookiejar"
+	"regexp"
+	"strconv"
 	"strings"
 	"time"
 
@@ -38,6 +40,8 @@ var (
 		"Last-Modified",
 	}
 
+	contentRangeRe = regexp.MustCompile(`^bytes ((\d+)-(\d+)|\*)/(\d+|\*)$`)
+
 	// For tests
 	redirectAllRequestsTo string
 )
@@ -225,8 +229,46 @@ func requestImage(ctx context.Context, imageURL string, opts DownloadOptions) (*
 		return nil, func() {}, &ErrorNotModified{Message: "Not Modified", Headers: headersToStore(res)}
 	}
 
-	if res.StatusCode != 200 {
-		body, _ := io.ReadAll(res.Body)
+	// If the source responds with 206, check if the response contains entire image.
+	// If not, return an error.
+	if res.StatusCode == http.StatusPartialContent {
+		contentRange := res.Header.Get("Content-Range")
+		rangeParts := contentRangeRe.FindStringSubmatch(contentRange)
+		if len(rangeParts) == 0 {
+			res.Body.Close()
+			reqCancel()
+			return nil, func() {}, ierrors.New(404, "Partial response with invalid Content-Range header", msgSourceImageIsUnreachable)
+		}
+
+		if rangeParts[1] == "*" || rangeParts[2] != "0" {
+			res.Body.Close()
+			reqCancel()
+			return nil, func() {}, ierrors.New(404, "Partial response with incomplete content", msgSourceImageIsUnreachable)
+		}
+
+		contentLengthStr := rangeParts[4]
+		if contentLengthStr == "*" {
+			contentLengthStr = res.Header.Get("Content-Length")
+		}
+
+		contentLength, _ := strconv.Atoi(contentLengthStr)
+		rangeEnd, _ := strconv.Atoi(rangeParts[3])
+
+		if contentLength <= 0 || rangeEnd != contentLength-1 {
+			res.Body.Close()
+			reqCancel()
+			return nil, func() {}, ierrors.New(404, "Partial response with incomplete content", msgSourceImageIsUnreachable)
+		}
+	} else if res.StatusCode != http.StatusOK {
+		var msg string
+
+		if strings.HasPrefix(res.Header.Get("Content-Type"), "text/") {
+			body, _ := io.ReadAll(io.LimitReader(res.Body, 1024))
+			msg = fmt.Sprintf("Status: %d; %s", res.StatusCode, string(body))
+		} else {
+			msg = fmt.Sprintf("Status: %d", res.StatusCode)
+		}
+
 		res.Body.Close()
 		reqCancel()
 
@@ -235,7 +277,6 @@ func requestImage(ctx context.Context, imageURL string, opts DownloadOptions) (*
 			status = 500
 		}
 
-		msg := fmt.Sprintf("Status: %d; %s", res.StatusCode, string(body))
 		return nil, func() {}, ierrors.New(status, msg, msgSourceImageIsUnreachable)
 	}