mirror of
				https://github.com/imgproxy/imgproxy.git
				synced 2025-10-30 23:08:02 +02:00 
			
		
		
		
	Merge branch 'master' into version/3
This commit is contained in:
		
							
								
								
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -17,6 +17,23 @@ | ||||
| - Removed `crop` resizing type, use [crop](./docs/generating_the_url.md#crop) processing option instead. | ||||
| - Dropped old libvips (<8.8) support. | ||||
|  | ||||
| ## [2.17.0] - 2021-09-07 | ||||
| ### Added | ||||
| - Wildcard support in `IMGPROXY_ALLOWED_SOURCES`. | ||||
|  | ||||
| ### Change | ||||
| - If the source URL contains the `IMGPROXY_BASE_URL` prefix, it won't be added. | ||||
|  | ||||
| ### Fix | ||||
| - (pro) Fix path prefix support in the `/info` handler. | ||||
|  | ||||
| ### Deprecated | ||||
| - The [basic URL format](https://docs.imgproxy.net/generating_the_url_basic) is deprecated and can be removed in future versions. Use [advanced URL format](https://docs.imgproxy.net/generating_the_url_advanced) instead. | ||||
|  | ||||
| ## [2.16.7] - 2021-07-20 | ||||
| ### Change | ||||
| - Reset DPI while stripping meta. | ||||
|  | ||||
| ## [2.16.6] - 2021-07-08 | ||||
| ### Fix | ||||
| - Fix performance regression in ICC profile handling. | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"math" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
|  | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| @@ -72,7 +73,7 @@ var ( | ||||
| 	IgnoreSslVerification bool | ||||
| 	DevelopmentErrorsMode bool | ||||
|  | ||||
| 	AllowedSources      []string | ||||
| 	AllowedSources      []*regexp.Regexp | ||||
| 	LocalFileSystemRoot string | ||||
| 	S3Enabled           bool | ||||
| 	S3Region            string | ||||
| @@ -200,7 +201,7 @@ func Reset() { | ||||
| 	IgnoreSslVerification = false | ||||
| 	DevelopmentErrorsMode = false | ||||
|  | ||||
| 	AllowedSources = make([]string, 0) | ||||
| 	AllowedSources = make([]*regexp.Regexp, 0) | ||||
| 	LocalFileSystemRoot = "" | ||||
| 	S3Enabled = false | ||||
| 	S3Region = "" | ||||
| @@ -290,7 +291,7 @@ func Configure() error { | ||||
|  | ||||
| 	configurators.Int(&MaxAnimationFrames, "IMGPROXY_MAX_ANIMATION_FRAMES") | ||||
|  | ||||
| 	configurators.StringSlice(&AllowedSources, "IMGPROXY_ALLOWED_SOURCES") | ||||
| 	configurators.Patterns(&AllowedSources, "IMGPROXY_ALLOWED_SOURCES") | ||||
|  | ||||
| 	configurators.Bool(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE") | ||||
| 	configurators.Bool(&PngInterlaced, "IMGPROXY_PNG_INTERLACED") | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"encoding/hex" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| @@ -184,3 +185,35 @@ func HexFile(b *[][]byte, filepath string) error { | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func Patterns(s *[]*regexp.Regexp, name string) { | ||||
| 	if env := os.Getenv(name); len(env) > 0 { | ||||
| 		parts := strings.Split(env, ",") | ||||
| 		result := make([]*regexp.Regexp, len(parts)) | ||||
|  | ||||
| 		for i, p := range parts { | ||||
| 			result[i] = RegexpFromPattern(strings.TrimSpace(p)) | ||||
| 		} | ||||
|  | ||||
| 		*s = result | ||||
| 	} else { | ||||
| 		*s = []*regexp.Regexp{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func RegexpFromPattern(pattern string) *regexp.Regexp { | ||||
| 	var result strings.Builder | ||||
| 	// Perform prefix matching | ||||
| 	result.WriteString("^") | ||||
| 	for i, part := range strings.Split(pattern, "*") { | ||||
| 		// Add a regexp match all without slashes for each wildcard character | ||||
| 		if i > 0 { | ||||
| 			result.WriteString("[^/]*") | ||||
| 		} | ||||
|  | ||||
| 		// Quote other parts of the pattern | ||||
| 		result.WriteString(regexp.QuoteMeta(part)) | ||||
| 	} | ||||
| 	// It is safe to use regexp.MustCompile since the expression is always valid | ||||
| 	return regexp.MustCompile(result.String()) | ||||
| } | ||||
|   | ||||
| @@ -73,7 +73,7 @@ imgproxy does not send CORS headers by default. Specify allowed origin to enable | ||||
|  | ||||
| You can limit allowed source URLs: | ||||
|  | ||||
| * `IMGPROXY_ALLOWED_SOURCES`: whitelist of source image URLs prefixes divided by comma. When blank, imgproxy allows all source image URLs. Example: `s3://,https://example.com/,local://`. Default: blank. | ||||
| * `IMGPROXY_ALLOWED_SOURCES`: whitelist of source image URLs prefixes divided by comma. Wildcards can be included with `*` to match all characters except `/`. When blank, imgproxy allows all source image URLs. Example: `s3://,https://*.example.com/,local://`. Default: blank. | ||||
|  | ||||
| **⚠️Warning:** Be careful when using this config to limit source URL hosts, and always add a trailing slash after the host. Bad: `http://example.com`, good: `http://example.com/`. If you don't add a trailing slash, `http://example.com@baddomain.com` will be an allowed URL but the request will be made to `baddomain.com`. | ||||
|  | ||||
| @@ -335,7 +335,7 @@ imgproxy can send logs to syslog, but this feature is disabled by default. To en | ||||
|  | ||||
| ## Miscellaneous | ||||
|  | ||||
| * `IMGPROXY_BASE_URL`: base URL prefix that will be added to every requested image URL. For example, if the base URL is `http://example.com/images` and `/path/to/image.png` is requested, imgproxy will download the source image from `http://example.com/images/path/to/image.png`. Default: blank. | ||||
| * `IMGPROXY_BASE_URL`: base URL prefix that will be added to every requested image URL. For example, if the base URL is `http://example.com/images` and `/path/to/image.png` is requested, imgproxy will download the source image from `http://example.com/images/path/to/image.png`. If the image URL already contains the prefix, it won't be added. Default: blank. | ||||
| * `IMGPROXY_USE_LINEAR_COLORSPACE`: when `true`, imgproxy will process images in linear colorspace. This will slow down processing. Note that images won't be fully processed in linear colorspace while shrink-on-load is enabled (see below). | ||||
| * `IMGPROXY_DISABLE_SHRINK_ON_LOAD`: when `true`, disables shrink-on-load for JPEG and WebP. Allows to process the whole image in linear colorspace but dramatically slows down resizing and increases memory usage when working with large images. | ||||
| * `IMGPROXY_STRIP_METADATA`: when `true`, imgproxy will strip all metadata (EXIF, IPTC, etc.) from JPEG and WebP output images. Default: `true`. | ||||
|   | ||||
| @@ -25,6 +25,9 @@ var ( | ||||
| 		"Cache-Control", | ||||
| 		"Expires", | ||||
| 	} | ||||
|  | ||||
| 	// For tests | ||||
| 	redirectAllRequestsTo string | ||||
| ) | ||||
|  | ||||
| const msgSourceImageIsUnreachable = "Source image is unreachable" | ||||
| @@ -103,6 +106,11 @@ func requestImage(imageURL string) (*http.Response, error) { | ||||
| } | ||||
|  | ||||
| func download(imageURL string) (*ImageData, error) { | ||||
| 	// We use this for testing | ||||
| 	if len(redirectAllRequestsTo) > 0 { | ||||
| 		imageURL = redirectAllRequestsTo | ||||
| 	} | ||||
|  | ||||
| 	res, err := requestImage(imageURL) | ||||
| 	if res != nil { | ||||
| 		defer res.Body.Close() | ||||
| @@ -140,3 +148,11 @@ func download(imageURL string) (*ImageData, error) { | ||||
|  | ||||
| 	return imgdata, nil | ||||
| } | ||||
|  | ||||
| func RedirectAllRequestsTo(u string) { | ||||
| 	redirectAllRequestsTo = u | ||||
| } | ||||
|  | ||||
| func StopRedirectingRequests() { | ||||
| 	redirectAllRequestsTo = "" | ||||
| } | ||||
|   | ||||
| @@ -14,7 +14,7 @@ var landingTmpl = []byte(` | ||||
| `) | ||||
|  | ||||
| func handleLanding(reqID string, rw http.ResponseWriter, r *http.Request) { | ||||
| 	rw.Header().Set("Content-Tyle", "text/html") | ||||
| 	rw.Header().Set("Content-Type", "text/html") | ||||
| 	rw.WriteHeader(200) | ||||
| 	rw.Write(landingTmpl) | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,14 @@ import ( | ||||
|  | ||||
| const urlTokenPlain = "plain" | ||||
|  | ||||
| func addBaseURL(u string) string { | ||||
| 	if len(config.BaseURL) == 0 || strings.HasPrefix(u, config.BaseURL) { | ||||
| 		return u | ||||
| 	} | ||||
|  | ||||
| 	return fmt.Sprintf("%s%s", config.BaseURL, u) | ||||
| } | ||||
|  | ||||
| func decodeBase64URL(parts []string) (string, string, error) { | ||||
| 	var format string | ||||
|  | ||||
| @@ -35,9 +43,7 @@ func decodeBase64URL(parts []string) (string, string, error) { | ||||
| 		return "", "", fmt.Errorf("Invalid url encoding: %s", encoded) | ||||
| 	} | ||||
|  | ||||
| 	fullURL := fmt.Sprintf("%s%s", config.BaseURL, string(imageURL)) | ||||
|  | ||||
| 	return fullURL, format, nil | ||||
| 	return addBaseURL(string(imageURL)), format, nil | ||||
| } | ||||
|  | ||||
| func decodePlainURL(parts []string) (string, string, error) { | ||||
| @@ -63,9 +69,7 @@ func decodePlainURL(parts []string) (string, string, error) { | ||||
| 		return "", "", fmt.Errorf("Invalid url encoding: %s", encoded) | ||||
| 	} | ||||
|  | ||||
| 	fullURL := fmt.Sprintf("%s%s", config.BaseURL, unescaped) | ||||
|  | ||||
| 	return fullURL, format, nil | ||||
| 	return addBaseURL(unescaped), format, nil | ||||
| } | ||||
|  | ||||
| func DecodeURL(parts []string) (string, string, error) { | ||||
|   | ||||
| @@ -7,9 +7,12 @@ import ( | ||||
| 	"net/http/httptest" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"regexp" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/imgproxy/imgproxy/v2/config" | ||||
| 	"github.com/imgproxy/imgproxy/v2/config/configurators" | ||||
| 	"github.com/imgproxy/imgproxy/v2/imagedata" | ||||
| 	"github.com/imgproxy/imgproxy/v2/imagemeta" | ||||
| 	"github.com/imgproxy/imgproxy/v2/imagetype" | ||||
| 	"github.com/imgproxy/imgproxy/v2/router" | ||||
| @@ -115,22 +118,66 @@ func (s *ProcessingHandlerTestSuite) TestSignatureValidationSuccess() { | ||||
| 	assert.Equal(s.T(), 200, res.StatusCode) | ||||
| } | ||||
|  | ||||
| func (s *ProcessingHandlerTestSuite) TestSourceValidationFailure() { | ||||
| 	config.AllowedSources = []string{"https://"} | ||||
| func (s *ProcessingHandlerTestSuite) TestSourceValidation() { | ||||
| 	imagedata.RedirectAllRequestsTo("local:///test1.png") | ||||
| 	defer imagedata.StopRedirectingRequests() | ||||
|  | ||||
| 	rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png") | ||||
| 	res := rw.Result() | ||||
| 	tt := []struct { | ||||
| 		name           string | ||||
| 		allowedSources []string | ||||
| 		requestPath    string | ||||
| 		expectedError  bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:           "match http URL without wildcard", | ||||
| 			allowedSources: []string{"local://", "http://images.dev/"}, | ||||
| 			requestPath:    "/unsafe/plain/http://images.dev/lorem/ipsum.jpg", | ||||
| 			expectedError:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "match http URL with wildcard in hostname single level", | ||||
| 			allowedSources: []string{"local://", "http://*.mycdn.dev/"}, | ||||
| 			requestPath:    "/unsafe/plain/http://a-1.mycdn.dev/lorem/ipsum.jpg", | ||||
| 			expectedError:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "match http URL with wildcard in hostname multiple levels", | ||||
| 			allowedSources: []string{"local://", "http://*.mycdn.dev/"}, | ||||
| 			requestPath:    "/unsafe/plain/http://a-1.b-2.mycdn.dev/lorem/ipsum.jpg", | ||||
| 			expectedError:  false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "no match s3 URL with allowed local and http URLs", | ||||
| 			allowedSources: []string{"local://", "http://images.dev/"}, | ||||
| 			requestPath:    "/unsafe/plain/s3://images/lorem/ipsum.jpg", | ||||
| 			expectedError:  true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:           "no match http URL with wildcard in hostname including slash", | ||||
| 			allowedSources: []string{"local://", "http://*.mycdn.dev/"}, | ||||
| 			requestPath:    "/unsafe/plain/http://other.dev/.mycdn.dev/lorem/ipsum.jpg", | ||||
| 			expectedError:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	assert.Equal(s.T(), 404, res.StatusCode) | ||||
| } | ||||
| 	for _, tc := range tt { | ||||
| 		s.T().Run(tc.name, func(t *testing.T) { | ||||
| 			exps := make([]*regexp.Regexp, len(tc.allowedSources)) | ||||
| 			for i, pattern := range tc.allowedSources { | ||||
| 				exps[i] = configurators.RegexpFromPattern(pattern) | ||||
| 			} | ||||
| 			config.AllowedSources = exps | ||||
|  | ||||
| func (s *ProcessingHandlerTestSuite) TestSourceValidationSuccess() { | ||||
| 	config.AllowedSources = []string{"local:///"} | ||||
| 			rw := s.send(tc.requestPath) | ||||
| 			res := rw.Result() | ||||
|  | ||||
| 	rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png") | ||||
| 	res := rw.Result() | ||||
|  | ||||
| 	assert.Equal(s.T(), 200, res.StatusCode) | ||||
| 			if tc.expectedError { | ||||
| 				assert.Equal(s.T(), 404, res.StatusCode) | ||||
| 			} else { | ||||
| 				assert.Equal(s.T(), 200, res.StatusCode) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *ProcessingHandlerTestSuite) TestSourceFormatNotSupported() { | ||||
|   | ||||
| @@ -1,8 +1,6 @@ | ||||
| package security | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/imgproxy/imgproxy/v2/config" | ||||
| ) | ||||
|  | ||||
| @@ -10,8 +8,8 @@ func VerifySourceURL(imageURL string) bool { | ||||
| 	if len(config.AllowedSources) == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 	for _, val := range config.AllowedSources { | ||||
| 		if strings.HasPrefix(imageURL, string(val)) { | ||||
| 	for _, allowedSource := range config.AllowedSources { | ||||
| 		if allowedSource.MatchString(imageURL) { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
|   | ||||
| @@ -523,7 +523,14 @@ vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n) { | ||||
|  | ||||
| int | ||||
| vips_strip(VipsImage *in, VipsImage **out) { | ||||
|   if (vips_copy(in, out, NULL)) return 1; | ||||
|   static double default_resolution = 72.0 / 25.4; | ||||
|  | ||||
|   if (vips_copy( | ||||
|     in, out, | ||||
|     "xres", default_resolution, | ||||
|     "yres", default_resolution, | ||||
|     NULL | ||||
|   )) return 1; | ||||
|  | ||||
|   gchar **fields = vips_image_get_fields(in); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user