mirror of
				https://github.com/imgproxy/imgproxy.git
				synced 2025-10-30 23:08:02 +02:00 
			
		
		
		
	Add IMGPROXY_MAX_RESULT_DIMENSION config and max_result_dimension processing option
				
					
				
			This commit is contained in:
		| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| ## [Unreleased] | ||||
| ### Added | ||||
| - Add [IMGPROXY_MAX_RESULT_DIMENSION](https://docs.imgproxy.net/latest/configuration/options#IMGPROXY_MAX_RESULT_DIMENSION) config and [max_result_dimension](https://docs.imgproxy.net/latest/usage/processing#max-result-dimension) processing option. | ||||
| - Add `imgproxy.source_image_origin` attribute to New Relic, DataDog, and OpenTelemetry traces. | ||||
| - Add `imgproxy.source_image_url` and `imgproxy.source_image_origin` attributes to `downloading_image` spans in New Relic, DataDog, and OpenTelemetry traces. | ||||
| - Add `imgproxy.processing_options` attribute to `processing_image` spans in New Relic, DataDog, and OpenTelemetry traces. | ||||
|   | ||||
| @@ -47,6 +47,7 @@ var ( | ||||
| 	MaxRedirects                int | ||||
| 	PngUnlimited                bool | ||||
| 	SvgUnlimited                bool | ||||
| 	MaxResultDimension          int | ||||
| 	AllowSecurityOptions        bool | ||||
|  | ||||
| 	JpegProgressive       bool | ||||
| @@ -252,6 +253,7 @@ func Reset() { | ||||
| 	MaxRedirects = 10 | ||||
| 	PngUnlimited = false | ||||
| 	SvgUnlimited = false | ||||
| 	MaxResultDimension = 0 | ||||
| 	AllowSecurityOptions = false | ||||
|  | ||||
| 	JpegProgressive = false | ||||
| @@ -483,6 +485,8 @@ func Configure() error { | ||||
| 	configurators.Bool(&PngUnlimited, "IMGPROXY_PNG_UNLIMITED") | ||||
| 	configurators.Bool(&SvgUnlimited, "IMGPROXY_SVG_UNLIMITED") | ||||
|  | ||||
| 	configurators.Int(&MaxResultDimension, "IMGPROXY_MAX_RESULT_DIMENSION") | ||||
|  | ||||
| 	configurators.Bool(&AllowSecurityOptions, "IMGPROXY_ALLOW_SECURITY_OPTIONS") | ||||
|  | ||||
| 	configurators.Bool(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE") | ||||
|   | ||||
| @@ -966,6 +966,24 @@ func applyMaxAnimationFrameResolutionOption(po *ProcessingOptions, args []string | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func applyMaxResultDimensionOption(po *ProcessingOptions, args []string) error { | ||||
| 	if err := security.IsSecurityOptionsAllowed(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if len(args) > 1 { | ||||
| 		return newOptionArgumentError("Invalid max_result_dimension arguments: %v", args) | ||||
| 	} | ||||
|  | ||||
| 	if x, err := strconv.Atoi(args[0]); err == nil { | ||||
| 		po.SecurityOptions.MaxResultDimension = x | ||||
| 	} else { | ||||
| 		return newOptionArgumentError("Invalid max_result_dimension: %s", args[0]) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func applyURLOption(po *ProcessingOptions, name string, args []string, usedPresets ...string) error { | ||||
| 	switch name { | ||||
| 	case "resize", "rs": | ||||
| @@ -1056,6 +1074,8 @@ func applyURLOption(po *ProcessingOptions, name string, args []string, usedPrese | ||||
| 		return applyMaxAnimationFramesOption(po, args) | ||||
| 	case "max_animation_frame_resolution", "mafr": | ||||
| 		return applyMaxAnimationFrameResolutionOption(po, args) | ||||
| 	case "max_result_dimension", "mrd": | ||||
| 		return applyMaxResultDimensionOption(po, args) | ||||
| 	} | ||||
|  | ||||
| 	return newUnknownOptionError("processing", name) | ||||
|   | ||||
| @@ -204,6 +204,50 @@ func (pctx *pipelineContext) calcSizes(widthToScale, heightToScale int, po *opti | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (pctx *pipelineContext) limitScale(widthToScale, heightToScale int, po *options.ProcessingOptions) { | ||||
| 	maxresultDim := po.SecurityOptions.MaxResultDimension | ||||
|  | ||||
| 	if maxresultDim <= 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	outWidth := imath.MinNonZero(pctx.scaledWidth, pctx.resultCropWidth) | ||||
| 	outHeight := imath.MinNonZero(pctx.scaledHeight, pctx.resultCropHeight) | ||||
|  | ||||
| 	if po.Extend.Enabled { | ||||
| 		outWidth = imath.Max(outWidth, pctx.targetWidth) | ||||
| 		outHeight = imath.Max(outHeight, pctx.targetHeight) | ||||
| 	} else if po.ExtendAspectRatio.Enabled { | ||||
| 		outWidth = imath.Max(outWidth, pctx.extendAspectRatioWidth) | ||||
| 		outHeight = imath.Max(outHeight, pctx.extendAspectRatioHeight) | ||||
| 	} | ||||
|  | ||||
| 	if po.Padding.Enabled { | ||||
| 		outWidth += imath.ScaleToEven(po.Padding.Left, pctx.dprScale) + imath.ScaleToEven(po.Padding.Right, pctx.dprScale) | ||||
| 		outHeight += imath.ScaleToEven(po.Padding.Top, pctx.dprScale) + imath.ScaleToEven(po.Padding.Bottom, pctx.dprScale) | ||||
| 	} | ||||
|  | ||||
| 	if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) { | ||||
| 		downScale := float64(maxresultDim) / float64(imath.Max(outWidth, outHeight)) | ||||
|  | ||||
| 		pctx.wscale *= downScale | ||||
| 		pctx.hscale *= downScale | ||||
|  | ||||
| 		// Prevent scaling below 1px | ||||
| 		if minWScale := 1.0 / float64(widthToScale); pctx.wscale < minWScale { | ||||
| 			pctx.wscale = minWScale | ||||
| 		} | ||||
| 		if minHScale := 1.0 / float64(heightToScale); pctx.hscale < minHScale { | ||||
| 			pctx.hscale = minHScale | ||||
| 		} | ||||
|  | ||||
| 		pctx.dprScale *= downScale | ||||
|  | ||||
| 		// Recalculate the sizes after changing the scales | ||||
| 		pctx.calcSizes(widthToScale, heightToScale, po) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { | ||||
| 	pctx.imgtype = imagetype.Unknown | ||||
| 	if imgdata != nil { | ||||
| @@ -233,5 +277,7 @@ func prepare(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptio | ||||
|  | ||||
| 	pctx.calcSizes(widthToScale, heightToScale, po) | ||||
|  | ||||
| 	pctx.limitScale(widthToScale, heightToScale, po) | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
| @@ -560,6 +560,435 @@ func (s *ProcessingTestSuite) TestResizeToFillDownExtendAR() { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *ProcessingTestSuite) TestResultSizeLimit() { | ||||
| 	imgdata := s.openFile("test2.jpg") | ||||
|  | ||||
| 	po := options.NewProcessingOptions() | ||||
|  | ||||
| 	testCases := []struct { | ||||
| 		limit        int | ||||
| 		width        int | ||||
| 		height       int | ||||
| 		resizingType options.ResizeType | ||||
| 		enlarge      bool | ||||
| 		extend       bool | ||||
| 		extendAR     bool | ||||
| 		padding      options.PaddingOptions | ||||
| 		rotate       int | ||||
| 		outWidth     int | ||||
| 		outHeight    int | ||||
| 	}{ | ||||
| 		{ | ||||
| 			limit:        1000, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        150, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			enlarge:      true, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			extend:       true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			extendAR:     true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        100, | ||||
| 			height:       150, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        200, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFit, | ||||
| 			padding: options.PaddingOptions{ | ||||
| 				Enabled: true, | ||||
| 				Top:     100, | ||||
| 				Right:   200, | ||||
| 				Bottom:  300, | ||||
| 				Left:    400, | ||||
| 			}, | ||||
| 			outWidth:  200, | ||||
| 			outHeight: 129, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        1000, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        1000, | ||||
| 			height:       50, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    13, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        100, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        150, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			enlarge:      true, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			extend:       true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			extendAR:     true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        100, | ||||
| 			height:       150, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     67, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        200, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFill, | ||||
| 			padding: options.PaddingOptions{ | ||||
| 				Enabled: true, | ||||
| 				Top:     100, | ||||
| 				Right:   200, | ||||
| 				Bottom:  300, | ||||
| 				Left:    400, | ||||
| 			}, | ||||
| 			outWidth:  200, | ||||
| 			outHeight: 144, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        1000, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        1000, | ||||
| 			height:       50, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    3, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        100, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     5, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    50, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        50, | ||||
| 			width:        150, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    25, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			enlarge:      true, | ||||
| 			outWidth:     100, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			extend:       true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       2000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			extendAR:     true, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        1000, | ||||
| 			height:       1500, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     67, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        100, | ||||
| 			width:        0, | ||||
| 			height:       0, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			rotate:       90, | ||||
| 			outWidth:     50, | ||||
| 			outHeight:    100, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        200, | ||||
| 			width:        100, | ||||
| 			height:       100, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			padding: options.PaddingOptions{ | ||||
| 				Enabled: true, | ||||
| 				Top:     100, | ||||
| 				Right:   200, | ||||
| 				Bottom:  300, | ||||
| 				Left:    400, | ||||
| 			}, | ||||
| 			outWidth:  200, | ||||
| 			outHeight: 144, | ||||
| 		}, | ||||
| 		{ | ||||
| 			limit:        200, | ||||
| 			width:        1000, | ||||
| 			height:       1000, | ||||
| 			resizingType: options.ResizeFillDown, | ||||
| 			padding: options.PaddingOptions{ | ||||
| 				Enabled: true, | ||||
| 				Top:     100, | ||||
| 				Right:   200, | ||||
| 				Bottom:  300, | ||||
| 				Left:    400, | ||||
| 			}, | ||||
| 			outWidth:  200, | ||||
| 			outHeight: 144, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range testCases { | ||||
| 		name := fmt.Sprintf("%s_%dx%d_limit_%d", tc.resizingType, tc.width, tc.height, tc.limit) | ||||
| 		if tc.enlarge { | ||||
| 			name += "_enlarge" | ||||
| 		} | ||||
| 		if tc.extend { | ||||
| 			name += "_extend" | ||||
| 		} | ||||
| 		if tc.extendAR { | ||||
| 			name += "_extendAR" | ||||
| 		} | ||||
| 		if tc.rotate != 0 { | ||||
| 			name += fmt.Sprintf("_rot_%d", tc.rotate) | ||||
| 		} | ||||
| 		if tc.padding.Enabled { | ||||
| 			name += fmt.Sprintf("_padding_%dx%dx%dx%d", tc.padding.Top, tc.padding.Right, tc.padding.Bottom, tc.padding.Left) | ||||
| 		} | ||||
|  | ||||
| 		s.Run(name, func() { | ||||
| 			po.SecurityOptions.MaxResultDimension = tc.limit | ||||
| 			po.Width = tc.width | ||||
| 			po.Height = tc.height | ||||
| 			po.ResizingType = tc.resizingType | ||||
| 			po.Enlarge = tc.enlarge | ||||
| 			po.Extend.Enabled = tc.extend | ||||
| 			po.ExtendAspectRatio.Enabled = tc.extendAR | ||||
| 			po.Rotate = tc.rotate | ||||
| 			po.Padding = tc.padding | ||||
|  | ||||
| 			outImgdata, err := ProcessImage(context.Background(), imgdata, po) | ||||
| 			s.Require().NoError(err) | ||||
| 			s.Require().NotNil(outImgdata) | ||||
|  | ||||
| 			s.checkSize(outImgdata, tc.outWidth, tc.outHeight) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestProcessing(t *testing.T) { | ||||
| 	suite.Run(t, new(ProcessingTestSuite)) | ||||
| } | ||||
|   | ||||
| @@ -9,6 +9,7 @@ type Options struct { | ||||
| 	MaxSrcFileSize              int | ||||
| 	MaxAnimationFrames          int | ||||
| 	MaxAnimationFrameResolution int | ||||
| 	MaxResultDimension          int | ||||
| } | ||||
|  | ||||
| func DefaultOptions() Options { | ||||
| @@ -17,6 +18,7 @@ func DefaultOptions() Options { | ||||
| 		MaxSrcFileSize:              config.MaxSrcFileSize, | ||||
| 		MaxAnimationFrames:          config.MaxAnimationFrames, | ||||
| 		MaxAnimationFrameResolution: config.MaxAnimationFrameResolution, | ||||
| 		MaxResultDimension:          config.MaxResultDimension, | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user