mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-28 20:43:54 +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.
|
- Removed `crop` resizing type, use [crop](./docs/generating_the_url.md#crop) processing option instead.
|
||||||
- Dropped old libvips (<8.8) support.
|
- 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
|
## [2.16.6] - 2021-07-08
|
||||||
### Fix
|
### Fix
|
||||||
- Fix performance regression in ICC profile handling.
|
- Fix performance regression in ICC profile handling.
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
@@ -72,7 +73,7 @@ var (
|
|||||||
IgnoreSslVerification bool
|
IgnoreSslVerification bool
|
||||||
DevelopmentErrorsMode bool
|
DevelopmentErrorsMode bool
|
||||||
|
|
||||||
AllowedSources []string
|
AllowedSources []*regexp.Regexp
|
||||||
LocalFileSystemRoot string
|
LocalFileSystemRoot string
|
||||||
S3Enabled bool
|
S3Enabled bool
|
||||||
S3Region string
|
S3Region string
|
||||||
@@ -200,7 +201,7 @@ func Reset() {
|
|||||||
IgnoreSslVerification = false
|
IgnoreSslVerification = false
|
||||||
DevelopmentErrorsMode = false
|
DevelopmentErrorsMode = false
|
||||||
|
|
||||||
AllowedSources = make([]string, 0)
|
AllowedSources = make([]*regexp.Regexp, 0)
|
||||||
LocalFileSystemRoot = ""
|
LocalFileSystemRoot = ""
|
||||||
S3Enabled = false
|
S3Enabled = false
|
||||||
S3Region = ""
|
S3Region = ""
|
||||||
@@ -290,7 +291,7 @@ func Configure() error {
|
|||||||
|
|
||||||
configurators.Int(&MaxAnimationFrames, "IMGPROXY_MAX_ANIMATION_FRAMES")
|
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(&JpegProgressive, "IMGPROXY_JPEG_PROGRESSIVE")
|
||||||
configurators.Bool(&PngInterlaced, "IMGPROXY_PNG_INTERLACED")
|
configurators.Bool(&PngInterlaced, "IMGPROXY_PNG_INTERLACED")
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -184,3 +185,35 @@ func HexFile(b *[][]byte, filepath string) error {
|
|||||||
|
|
||||||
return nil
|
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:
|
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`.
|
**⚠️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
|
## 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_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_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`.
|
* `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",
|
"Cache-Control",
|
||||||
"Expires",
|
"Expires",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For tests
|
||||||
|
redirectAllRequestsTo string
|
||||||
)
|
)
|
||||||
|
|
||||||
const msgSourceImageIsUnreachable = "Source image is unreachable"
|
const msgSourceImageIsUnreachable = "Source image is unreachable"
|
||||||
@@ -103,6 +106,11 @@ func requestImage(imageURL string) (*http.Response, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func download(imageURL string) (*ImageData, error) {
|
func download(imageURL string) (*ImageData, error) {
|
||||||
|
// We use this for testing
|
||||||
|
if len(redirectAllRequestsTo) > 0 {
|
||||||
|
imageURL = redirectAllRequestsTo
|
||||||
|
}
|
||||||
|
|
||||||
res, err := requestImage(imageURL)
|
res, err := requestImage(imageURL)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
@@ -140,3 +148,11 @@ func download(imageURL string) (*ImageData, error) {
|
|||||||
|
|
||||||
return imgdata, nil
|
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) {
|
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.WriteHeader(200)
|
||||||
rw.Write(landingTmpl)
|
rw.Write(landingTmpl)
|
||||||
}
|
}
|
||||||
|
@@ -12,6 +12,14 @@ import (
|
|||||||
|
|
||||||
const urlTokenPlain = "plain"
|
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) {
|
func decodeBase64URL(parts []string) (string, string, error) {
|
||||||
var format string
|
var format string
|
||||||
|
|
||||||
@@ -35,9 +43,7 @@ func decodeBase64URL(parts []string) (string, string, error) {
|
|||||||
return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
|
return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullURL := fmt.Sprintf("%s%s", config.BaseURL, string(imageURL))
|
return addBaseURL(string(imageURL)), format, nil
|
||||||
|
|
||||||
return fullURL, format, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodePlainURL(parts []string) (string, string, error) {
|
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)
|
return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullURL := fmt.Sprintf("%s%s", config.BaseURL, unescaped)
|
return addBaseURL(unescaped), format, nil
|
||||||
|
|
||||||
return fullURL, format, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeURL(parts []string) (string, string, error) {
|
func DecodeURL(parts []string) (string, string, error) {
|
||||||
|
@@ -7,9 +7,12 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v2/config"
|
"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/imagemeta"
|
||||||
"github.com/imgproxy/imgproxy/v2/imagetype"
|
"github.com/imgproxy/imgproxy/v2/imagetype"
|
||||||
"github.com/imgproxy/imgproxy/v2/router"
|
"github.com/imgproxy/imgproxy/v2/router"
|
||||||
@@ -115,22 +118,66 @@ func (s *ProcessingHandlerTestSuite) TestSignatureValidationSuccess() {
|
|||||||
assert.Equal(s.T(), 200, res.StatusCode)
|
assert.Equal(s.T(), 200, res.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) TestSourceValidationFailure() {
|
func (s *ProcessingHandlerTestSuite) TestSourceValidation() {
|
||||||
config.AllowedSources = []string{"https://"}
|
imagedata.RedirectAllRequestsTo("local:///test1.png")
|
||||||
|
defer imagedata.StopRedirectingRequests()
|
||||||
|
|
||||||
rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
|
tt := []struct {
|
||||||
res := rw.Result()
|
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() {
|
rw := s.send(tc.requestPath)
|
||||||
config.AllowedSources = []string{"local:///"}
|
res := rw.Result()
|
||||||
|
|
||||||
rw := s.send("/unsafe/rs:fill:4:4/plain/local:///test1.png")
|
if tc.expectedError {
|
||||||
res := rw.Result()
|
assert.Equal(s.T(), 404, res.StatusCode)
|
||||||
|
} else {
|
||||||
assert.Equal(s.T(), 200, res.StatusCode)
|
assert.Equal(s.T(), 200, res.StatusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProcessingHandlerTestSuite) TestSourceFormatNotSupported() {
|
func (s *ProcessingHandlerTestSuite) TestSourceFormatNotSupported() {
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/imgproxy/imgproxy/v2/config"
|
"github.com/imgproxy/imgproxy/v2/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -10,8 +8,8 @@ func VerifySourceURL(imageURL string) bool {
|
|||||||
if len(config.AllowedSources) == 0 {
|
if len(config.AllowedSources) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
for _, val := range config.AllowedSources {
|
for _, allowedSource := range config.AllowedSources {
|
||||||
if strings.HasPrefix(imageURL, string(val)) {
|
if allowedSource.MatchString(imageURL) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -523,7 +523,14 @@ vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n) {
|
|||||||
|
|
||||||
int
|
int
|
||||||
vips_strip(VipsImage *in, VipsImage **out) {
|
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);
|
gchar **fields = vips_image_get_fields(in);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user