mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-09-16 09:36:18 +02:00
Revised errors for better error reporting
This commit is contained in:
67
errors.go
Normal file
67
errors.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type (
|
||||
ResponseWriteError struct{ error }
|
||||
InvalidURLError string
|
||||
TooManyRequestsError struct{}
|
||||
InvalidSecretError struct{}
|
||||
)
|
||||
|
||||
func newResponseWriteError(cause error) *ierrors.Error {
|
||||
return ierrors.Wrap(
|
||||
ResponseWriteError{cause},
|
||||
1,
|
||||
ierrors.WithPublicMessage("Failed to write response"),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ResponseWriteError) Error() string {
|
||||
return fmt.Sprintf("Failed to write response: %s", e.error)
|
||||
}
|
||||
|
||||
func (e ResponseWriteError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func newInvalidURLErrorf(status int, format string, args ...interface{}) error {
|
||||
return ierrors.Wrap(
|
||||
InvalidURLError(fmt.Sprintf(format, args...)),
|
||||
1,
|
||||
ierrors.WithStatusCode(status),
|
||||
ierrors.WithPublicMessage("Invalid URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e InvalidURLError) Error() string { return string(e) }
|
||||
|
||||
func newTooManyRequestsError() error {
|
||||
return ierrors.Wrap(
|
||||
TooManyRequestsError{},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusTooManyRequests),
|
||||
ierrors.WithPublicMessage("Too many requests"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e TooManyRequestsError) Error() string { return "Too many requests" }
|
||||
|
||||
func newInvalidSecretError() error {
|
||||
return ierrors.Wrap(
|
||||
InvalidSecretError{},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusForbidden),
|
||||
ierrors.WithPublicMessage("Forbidden"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e InvalidSecretError) Error() string { return "Invalid secret" }
|
@@ -2,83 +2,147 @@ package ierrors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Option func(*Error)
|
||||
|
||||
type Error struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
PublicMessage string
|
||||
Unexpected bool
|
||||
err error
|
||||
|
||||
prefix string
|
||||
statusCode int
|
||||
publicMessage string
|
||||
shouldReport bool
|
||||
|
||||
stack []uintptr
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *Error) FormatStack() string {
|
||||
if e.stack == nil {
|
||||
return ""
|
||||
if len(e.prefix) > 0 {
|
||||
return fmt.Sprintf("%s: %s", e.prefix, e.err.Error())
|
||||
}
|
||||
|
||||
return formatStack(e.stack)
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *Error) Cause() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (e *Error) StatusCode() int {
|
||||
if e.statusCode <= 0 {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
|
||||
return e.statusCode
|
||||
}
|
||||
|
||||
func (e *Error) PublicMessage() string {
|
||||
if len(e.publicMessage) == 0 {
|
||||
return "Internal error"
|
||||
}
|
||||
|
||||
return e.publicMessage
|
||||
}
|
||||
|
||||
func (e *Error) ShouldReport() bool {
|
||||
return e.shouldReport
|
||||
}
|
||||
|
||||
func (e *Error) StackTrace() []uintptr {
|
||||
return e.stack
|
||||
}
|
||||
|
||||
func New(status int, msg string, pub string) *Error {
|
||||
return &Error{
|
||||
StatusCode: status,
|
||||
Message: msg,
|
||||
PublicMessage: pub,
|
||||
}
|
||||
func (e *Error) Callers() []uintptr {
|
||||
return e.stack
|
||||
}
|
||||
|
||||
func NewUnexpected(msg string, skip int) *Error {
|
||||
return &Error{
|
||||
StatusCode: 500,
|
||||
Message: msg,
|
||||
PublicMessage: "Internal error",
|
||||
Unexpected: true,
|
||||
func (e *Error) FormatStackLines() []string {
|
||||
lines := make([]string, len(e.stack))
|
||||
|
||||
stack: callers(skip + 3),
|
||||
}
|
||||
}
|
||||
|
||||
func Wrap(err error, skip int) *Error {
|
||||
if ierr, ok := err.(*Error); ok {
|
||||
return ierr
|
||||
}
|
||||
return NewUnexpected(err.Error(), skip+1)
|
||||
}
|
||||
|
||||
func WrapWithPrefix(err error, skip int, prefix string) *Error {
|
||||
if ierr, ok := err.(*Error); ok {
|
||||
newErr := *ierr
|
||||
newErr.Message = fmt.Sprintf("%s: %s", prefix, ierr.Message)
|
||||
return &newErr
|
||||
}
|
||||
return NewUnexpected(fmt.Sprintf("%s: %s", prefix, err), skip+1)
|
||||
}
|
||||
|
||||
func callers(skip int) []uintptr {
|
||||
stack := make([]uintptr, 10)
|
||||
n := runtime.Callers(skip, stack)
|
||||
return stack[:n]
|
||||
}
|
||||
|
||||
func formatStack(stack []uintptr) string {
|
||||
lines := make([]string, len(stack))
|
||||
for i, pc := range stack {
|
||||
for i, pc := range e.stack {
|
||||
f := runtime.FuncForPC(pc)
|
||||
file, line := f.FileLine(pc)
|
||||
lines[i] = fmt.Sprintf("%s:%d %s", file, line, f.Name())
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
return lines
|
||||
}
|
||||
|
||||
func (e *Error) FormatStack() string {
|
||||
return strings.Join(e.FormatStackLines(), "\n")
|
||||
}
|
||||
|
||||
func Wrap(err error, stackSkip int, opts ...Option) *Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var e *Error
|
||||
|
||||
if ierr, ok := err.(*Error); ok {
|
||||
// if we have some options, we need to copy the error to not modify the original one
|
||||
if len(opts) > 0 {
|
||||
ecopy := *ierr
|
||||
e = &ecopy
|
||||
} else {
|
||||
return ierr
|
||||
}
|
||||
} else {
|
||||
e = &Error{
|
||||
err: err,
|
||||
shouldReport: true,
|
||||
}
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(e)
|
||||
}
|
||||
|
||||
if len(e.stack) == 0 {
|
||||
e.stack = callers(stackSkip + 1)
|
||||
}
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
func WithStatusCode(code int) Option {
|
||||
return func(e *Error) {
|
||||
e.statusCode = code
|
||||
}
|
||||
}
|
||||
|
||||
func WithPublicMessage(msg string) Option {
|
||||
return func(e *Error) {
|
||||
e.publicMessage = msg
|
||||
}
|
||||
}
|
||||
|
||||
func WithPrefix(prefix string) Option {
|
||||
return func(e *Error) {
|
||||
if len(e.prefix) > 0 {
|
||||
e.prefix = fmt.Sprintf("%s: %s", prefix, e.prefix)
|
||||
} else {
|
||||
e.prefix = prefix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func WithShouldReport(report bool) Option {
|
||||
return func(e *Error) {
|
||||
e.shouldReport = report
|
||||
}
|
||||
}
|
||||
|
||||
func callers(skip int) []uintptr {
|
||||
stack := make([]uintptr, 10)
|
||||
n := runtime.Callers(skip+2, stack)
|
||||
return stack[:n]
|
||||
}
|
||||
|
@@ -53,15 +53,6 @@ type DownloadOptions struct {
|
||||
CookieJar http.CookieJar
|
||||
}
|
||||
|
||||
type ErrorNotModified struct {
|
||||
Message string
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func (e *ErrorNotModified) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func initDownloading() error {
|
||||
transport, err := defaultTransport.New(true)
|
||||
if err != nil {
|
||||
@@ -143,16 +134,12 @@ func BuildImageRequest(ctx context.Context, imageURL string, header http.Header,
|
||||
req, err := http.NewRequestWithContext(reqCtx, "GET", imageURL, nil)
|
||||
if err != nil {
|
||||
reqCancel()
|
||||
return nil, func() {}, ierrors.New(404, err.Error(), msgSourceImageIsUnreachable)
|
||||
return nil, func() {}, newImageRequestError(err)
|
||||
}
|
||||
|
||||
if _, ok := enabledSchemes[req.URL.Scheme]; !ok {
|
||||
reqCancel()
|
||||
return nil, func() {}, ierrors.New(
|
||||
404,
|
||||
fmt.Sprintf("Unknown scheme: %s", req.URL.Scheme),
|
||||
msgSourceImageIsUnreachable,
|
||||
)
|
||||
return nil, func() {}, newImageRequstSchemeError(req.URL.Scheme)
|
||||
}
|
||||
|
||||
if jar != nil {
|
||||
@@ -226,7 +213,7 @@ func requestImage(ctx context.Context, imageURL string, opts DownloadOptions) (*
|
||||
if res.StatusCode == http.StatusNotModified {
|
||||
res.Body.Close()
|
||||
reqCancel()
|
||||
return nil, func() {}, &ErrorNotModified{Message: "Not Modified", Headers: headersToStore(res)}
|
||||
return nil, func() {}, newNotModifiedError(headersToStore(res))
|
||||
}
|
||||
|
||||
// If the source responds with 206, check if the response contains entire image.
|
||||
@@ -237,13 +224,13 @@ func requestImage(ctx context.Context, imageURL string, opts DownloadOptions) (*
|
||||
if len(rangeParts) == 0 {
|
||||
res.Body.Close()
|
||||
reqCancel()
|
||||
return nil, func() {}, ierrors.New(404, "Partial response with invalid Content-Range header", msgSourceImageIsUnreachable)
|
||||
return nil, func() {}, newImagePartialResponseError("Partial response with invalid Content-Range header")
|
||||
}
|
||||
|
||||
if rangeParts[1] == "*" || rangeParts[2] != "0" {
|
||||
res.Body.Close()
|
||||
reqCancel()
|
||||
return nil, func() {}, ierrors.New(404, "Partial response with incomplete content", msgSourceImageIsUnreachable)
|
||||
return nil, func() {}, newImagePartialResponseError("Partial response with incomplete content")
|
||||
}
|
||||
|
||||
contentLengthStr := rangeParts[4]
|
||||
@@ -257,27 +244,20 @@ func requestImage(ctx context.Context, imageURL string, opts DownloadOptions) (*
|
||||
if contentLength <= 0 || rangeEnd != contentLength-1 {
|
||||
res.Body.Close()
|
||||
reqCancel()
|
||||
return nil, func() {}, ierrors.New(404, "Partial response with incomplete content", msgSourceImageIsUnreachable)
|
||||
return nil, func() {}, newImagePartialResponseError("Partial response with incomplete content")
|
||||
}
|
||||
} else if res.StatusCode != http.StatusOK {
|
||||
var msg string
|
||||
var body 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)
|
||||
bbody, _ := io.ReadAll(io.LimitReader(res.Body, 1024))
|
||||
body = string(bbody)
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
reqCancel()
|
||||
|
||||
status := 404
|
||||
if res.StatusCode >= 500 {
|
||||
status = 500
|
||||
}
|
||||
|
||||
return nil, func() {}, ierrors.New(status, msg, msgSourceImageIsUnreachable)
|
||||
return nil, func() {}, newImageResponseStatusError(res.StatusCode, body)
|
||||
}
|
||||
|
||||
return res, reqCancel, nil
|
||||
|
@@ -1,53 +0,0 @@
|
||||
package imagedata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
)
|
||||
|
||||
type httpError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
isTimeout := false
|
||||
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
isTimeout = true
|
||||
case errors.Is(err, context.Canceled):
|
||||
return ierrors.New(
|
||||
499,
|
||||
fmt.Sprintf("The image request is cancelled: %s", err),
|
||||
msgSourceImageIsUnreachable,
|
||||
)
|
||||
case errors.Is(err, security.ErrSourceAddressNotAllowed), errors.Is(err, security.ErrInvalidSourceAddress):
|
||||
return ierrors.New(
|
||||
404,
|
||||
err.Error(),
|
||||
msgSourceImageIsUnreachable,
|
||||
)
|
||||
default:
|
||||
if httpErr, ok := err.(httpError); ok {
|
||||
isTimeout = httpErr.Timeout()
|
||||
}
|
||||
}
|
||||
|
||||
if !isTimeout {
|
||||
return err
|
||||
}
|
||||
|
||||
ierr := ierrors.New(
|
||||
http.StatusGatewayTimeout,
|
||||
fmt.Sprintf("The image request timed out: %s", err),
|
||||
msgSourceImageIsUnreachable,
|
||||
)
|
||||
ierr.Unexpected = true
|
||||
|
||||
return ierr
|
||||
}
|
170
imagedata/errors.go
Normal file
170
imagedata/errors.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package imagedata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
)
|
||||
|
||||
type (
|
||||
ImageRequestError struct{ error }
|
||||
ImageRequstSchemeError string
|
||||
ImagePartialResponseError string
|
||||
ImageResponseStatusError string
|
||||
ImageRequestCanceledError struct{ error }
|
||||
ImageRequestTimeoutError struct{ error }
|
||||
|
||||
NotModifiedError struct {
|
||||
headers map[string]string
|
||||
}
|
||||
|
||||
httpError interface {
|
||||
Timeout() bool
|
||||
}
|
||||
)
|
||||
|
||||
func newImageRequestError(err error) error {
|
||||
return ierrors.Wrap(
|
||||
ImageRequestError{err},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageRequestError) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
func newImageRequstSchemeError(scheme string) error {
|
||||
return ierrors.Wrap(
|
||||
ImageRequstSchemeError(fmt.Sprintf("Unknown scheme: %s", scheme)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageRequstSchemeError) Error() string { return string(e) }
|
||||
|
||||
func newImagePartialResponseError(msg string) error {
|
||||
return ierrors.Wrap(
|
||||
ImagePartialResponseError(msg),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImagePartialResponseError) Error() string { return string(e) }
|
||||
|
||||
func newImageResponseStatusError(status int, body string) error {
|
||||
var msg string
|
||||
|
||||
if len(body) > 0 {
|
||||
msg = fmt.Sprintf("Status: %d; %s", status, body)
|
||||
} else {
|
||||
msg = fmt.Sprintf("Status: %d", status)
|
||||
}
|
||||
|
||||
statusCode := 404
|
||||
if status >= 500 {
|
||||
statusCode = 500
|
||||
}
|
||||
|
||||
return ierrors.Wrap(
|
||||
ImageResponseStatusError(msg),
|
||||
1,
|
||||
ierrors.WithStatusCode(statusCode),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageResponseStatusError) Error() string { return string(e) }
|
||||
|
||||
func newImageRequestCanceledError(err error) error {
|
||||
return ierrors.Wrap(
|
||||
ImageRequestCanceledError{err},
|
||||
2,
|
||||
ierrors.WithStatusCode(499),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageRequestCanceledError) Error() string {
|
||||
return fmt.Sprintf("The image request is cancelled: %s", e.error)
|
||||
}
|
||||
|
||||
func (e ImageRequestCanceledError) Unwrap() error { return e.error }
|
||||
|
||||
func newImageRequestTimeoutError(err error) error {
|
||||
return ierrors.Wrap(
|
||||
ImageRequestTimeoutError{err},
|
||||
2,
|
||||
ierrors.WithStatusCode(http.StatusGatewayTimeout),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageRequestTimeoutError) Error() string {
|
||||
return fmt.Sprintf("The image request timed out: %s", e.error)
|
||||
}
|
||||
|
||||
func (e ImageRequestTimeoutError) Unwrap() error { return e.error }
|
||||
|
||||
func newNotModifiedError(headers map[string]string) error {
|
||||
return ierrors.Wrap(
|
||||
NotModifiedError{headers},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotModified),
|
||||
ierrors.WithPublicMessage("Not modified"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e NotModifiedError) Error() string { return "Not modified" }
|
||||
|
||||
func (e NotModifiedError) Headers() map[string]string {
|
||||
return e.headers
|
||||
}
|
||||
|
||||
func wrapError(err error) error {
|
||||
isTimeout := false
|
||||
|
||||
var secArrdErr security.SourceAddressError
|
||||
|
||||
switch {
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
isTimeout = true
|
||||
case errors.Is(err, context.Canceled):
|
||||
return newImageRequestCanceledError(err)
|
||||
case errors.As(err, &secArrdErr):
|
||||
return ierrors.Wrap(
|
||||
err,
|
||||
1,
|
||||
ierrors.WithStatusCode(404),
|
||||
ierrors.WithPublicMessage(msgSourceImageIsUnreachable),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
default:
|
||||
if httpErr, ok := err.(httpError); ok {
|
||||
isTimeout = httpErr.Timeout()
|
||||
}
|
||||
}
|
||||
|
||||
if isTimeout {
|
||||
return newImageRequestTimeoutError(err)
|
||||
}
|
||||
|
||||
return ierrors.Wrap(err, 1)
|
||||
}
|
@@ -133,11 +133,10 @@ func FromFile(path, desc string, secopts security.Options) (*ImageData, error) {
|
||||
func Download(ctx context.Context, imageURL, desc string, opts DownloadOptions, secopts security.Options) (*ImageData, error) {
|
||||
imgdata, err := download(ctx, imageURL, opts, secopts)
|
||||
if err != nil {
|
||||
if nmErr, ok := err.(*ErrorNotModified); ok {
|
||||
nmErr.Message = fmt.Sprintf("Can't download %s: %s", desc, nmErr.Message)
|
||||
return nil, nmErr
|
||||
}
|
||||
return nil, ierrors.WrapWithPrefix(err, 1, fmt.Sprintf("Can't download %s", desc))
|
||||
return nil, ierrors.Wrap(
|
||||
err, 0,
|
||||
ierrors.WithPrefix(fmt.Sprintf("Can't download %s", desc)),
|
||||
)
|
||||
}
|
||||
|
||||
return imgdata, nil
|
||||
|
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -162,7 +161,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusPartialContent() {
|
||||
|
||||
if tc.expectErr {
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode())
|
||||
} else {
|
||||
s.Require().NoError(err)
|
||||
s.Require().NotNil(imgdata)
|
||||
@@ -181,7 +180,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusNotFound() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -193,7 +192,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusForbidden() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -205,7 +204,7 @@ func (s *ImageDataTestSuite) TestDownloadStatusInternalServerError() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(500, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(500, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -219,7 +218,7 @@ func (s *ImageDataTestSuite) TestDownloadUnreachable() {
|
||||
imgdata, err := Download(context.Background(), serverURL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(500, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(500, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -229,18 +228,17 @@ func (s *ImageDataTestSuite) TestDownloadInvalidImage() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
func (s *ImageDataTestSuite) TestDownloadSourceAddressNotAllowed() {
|
||||
log.Printf("Server URL: %s", s.server.URL)
|
||||
config.AllowLoopbackSourceAddresses = false
|
||||
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(404, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -250,7 +248,7 @@ func (s *ImageDataTestSuite) TestDownloadImageTooLarge() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
@@ -260,7 +258,7 @@ func (s *ImageDataTestSuite) TestDownloadImageFileTooLarge() {
|
||||
imgdata, err := Download(context.Background(), s.server.URL, "Test image", DownloadOptions{}, security.DefaultOptions())
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode)
|
||||
s.Require().Equal(422, ierrors.Wrap(err, 0).StatusCode())
|
||||
s.Require().Nil(imgdata)
|
||||
}
|
||||
|
||||
|
@@ -8,13 +8,10 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/bufpool"
|
||||
"github.com/imgproxy/imgproxy/v3/bufreader"
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
"github.com/imgproxy/imgproxy/v3/imagemeta"
|
||||
"github.com/imgproxy/imgproxy/v3/security"
|
||||
)
|
||||
|
||||
var ErrSourceImageTypeNotSupported = ierrors.New(422, "Source image type not supported", "Invalid source image")
|
||||
|
||||
var downloadBufPool *bufpool.Pool
|
||||
|
||||
func initRead() {
|
||||
@@ -38,10 +35,6 @@ func readAndCheckImage(r io.Reader, contentLength int, secopts security.Options)
|
||||
buf.Reset()
|
||||
cancel()
|
||||
|
||||
if err == imagemeta.ErrFormat {
|
||||
return nil, ErrSourceImageTypeNotSupported
|
||||
}
|
||||
|
||||
return nil, wrapError(err)
|
||||
}
|
||||
|
||||
|
@@ -10,10 +10,6 @@ import (
|
||||
|
||||
var bmpMagick = []byte("BM")
|
||||
|
||||
type BmpFormatError string
|
||||
|
||||
func (e BmpFormatError) Error() string { return "invalid BMP format: " + string(e) }
|
||||
|
||||
func DecodeBmpMeta(r io.Reader) (Meta, error) {
|
||||
var tmp [26]byte
|
||||
|
||||
@@ -22,7 +18,7 @@ func DecodeBmpMeta(r io.Reader) (Meta, error) {
|
||||
}
|
||||
|
||||
if !bytes.Equal(tmp[:2], bmpMagick) {
|
||||
return nil, BmpFormatError("malformed header")
|
||||
return nil, newFormatError("BMP", "malformed header")
|
||||
}
|
||||
|
||||
infoSize := binary.LittleEndian.Uint32(tmp[14:18])
|
||||
|
37
imagemeta/errors.go
Normal file
37
imagemeta/errors.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package imagemeta
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type (
|
||||
UnknownFormatError struct{}
|
||||
FormatError string
|
||||
)
|
||||
|
||||
func newUnknownFormatError() error {
|
||||
return ierrors.Wrap(
|
||||
UnknownFormatError{},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
|
||||
ierrors.WithPublicMessage("Invalid source image"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e UnknownFormatError) Error() string { return "Source image type not supported" }
|
||||
|
||||
func newFormatError(format, msg string) error {
|
||||
return ierrors.Wrap(
|
||||
FormatError(fmt.Sprintf("Invalid %s file: %s", format, msg)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
|
||||
ierrors.WithPublicMessage("Invalid source image"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e FormatError) Error() string { return string(e) }
|
@@ -35,11 +35,11 @@ type heifData struct {
|
||||
|
||||
func (d *heifData) Meta() (*meta, error) {
|
||||
if d.Format == imagetype.Unknown {
|
||||
return nil, errors.New("Invalid HEIF file: format data wasn't found")
|
||||
return nil, newFormatError("HEIF", "format data wasn't found")
|
||||
}
|
||||
|
||||
if len(d.Sizes) == 0 {
|
||||
return nil, errors.New("Invalid HEIF file: dimensions data wasn't found")
|
||||
return nil, newFormatError("HEIF", "dimensions data wasn't found")
|
||||
}
|
||||
|
||||
bestSize := slices.MaxFunc(d.Sizes, func(a, b heifSize) int {
|
||||
@@ -64,6 +64,7 @@ func heifReadN(r io.Reader, n uint64) (b []byte, err error) {
|
||||
|
||||
b = make([]byte, n)
|
||||
_, err = io.ReadFull(r, b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ func heifReadBoxHeader(r io.Reader) (boxType string, boxDataSize uint64, err err
|
||||
}
|
||||
|
||||
if boxDataSize < heifBoxHeaderSize || boxDataSize > math.MaxInt64 {
|
||||
return "", 0, errors.New("Invalid box data size")
|
||||
return "", 0, newFormatError("HEIF", "invalid box data size")
|
||||
}
|
||||
|
||||
boxDataSize -= headerSize
|
||||
@@ -131,7 +132,7 @@ func heifAssignFormat(d *heifData, brand []byte) bool {
|
||||
|
||||
func heifReadFtyp(d *heifData, r io.Reader, boxDataSize uint64) error {
|
||||
if boxDataSize < 8 {
|
||||
return errors.New("Invalid ftyp data")
|
||||
return newFormatError("HEIF", "invalid ftyp data")
|
||||
}
|
||||
|
||||
data, err := heifReadN(r, boxDataSize)
|
||||
@@ -151,12 +152,12 @@ func heifReadFtyp(d *heifData, r io.Reader, boxDataSize uint64) error {
|
||||
}
|
||||
}
|
||||
|
||||
return errors.New("Image is not compatible with heic/avif")
|
||||
return newFormatError("HEIF", "image is not compatible with heic/avif")
|
||||
}
|
||||
|
||||
func heifReadMeta(d *heifData, r io.Reader, boxDataSize uint64) error {
|
||||
if boxDataSize < 4 {
|
||||
return errors.New("Invalid meta data")
|
||||
return newFormatError("HEIF", "invalid meta data")
|
||||
}
|
||||
|
||||
data, err := heifReadN(r, boxDataSize)
|
||||
@@ -165,7 +166,7 @@ func heifReadMeta(d *heifData, r io.Reader, boxDataSize uint64) error {
|
||||
}
|
||||
|
||||
if boxDataSize > 4 {
|
||||
if err := heifReadBoxes(d, bytes.NewBuffer(data[4:])); err != nil && err != io.EOF {
|
||||
if err := heifReadBoxes(d, bytes.NewBuffer(data[4:])); err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -175,7 +176,7 @@ func heifReadMeta(d *heifData, r io.Reader, boxDataSize uint64) error {
|
||||
|
||||
func heifReadHldr(r io.Reader, boxDataSize uint64) error {
|
||||
if boxDataSize < 12 {
|
||||
return errors.New("Invalid hdlr data")
|
||||
return newFormatError("HEIF", "invalid hdlr data")
|
||||
}
|
||||
|
||||
data, err := heifReadN(r, boxDataSize)
|
||||
@@ -192,7 +193,7 @@ func heifReadHldr(r io.Reader, boxDataSize uint64) error {
|
||||
|
||||
func heifReadIspe(r io.Reader, boxDataSize uint64) (w, h int64, err error) {
|
||||
if boxDataSize < 12 {
|
||||
return 0, 0, errors.New("Invalid ispe data")
|
||||
return 0, 0, newFormatError("HEIF", "invalid ispe data")
|
||||
}
|
||||
|
||||
data, err := heifReadN(r, boxDataSize)
|
||||
@@ -230,7 +231,7 @@ func heifReadBoxes(d *heifData, r io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := heifReadBoxes(d, bytes.NewBuffer(data)); err != nil && err != io.EOF {
|
||||
if err := heifReadBoxes(d, bytes.NewBuffer(data)); err != nil && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
case "ispe":
|
||||
|
@@ -48,8 +48,6 @@ type reader interface {
|
||||
var (
|
||||
formatsMu sync.Mutex
|
||||
atomicFormats atomic.Value
|
||||
|
||||
ErrFormat = errors.New("unknown image format")
|
||||
)
|
||||
|
||||
func asReader(r io.Reader) reader {
|
||||
@@ -84,7 +82,7 @@ func DecodeMeta(r io.Reader) (Meta, error) {
|
||||
formats, _ := atomicFormats.Load().([]format)
|
||||
|
||||
for _, f := range formats {
|
||||
if b, err := rr.Peek(len(f.magic)); err == nil || err == io.EOF {
|
||||
if b, err := rr.Peek(len(f.magic)); err == nil || errors.Is(err, io.EOF) {
|
||||
if matchMagic(f.magic, b) {
|
||||
return f.decodeMeta(rr)
|
||||
}
|
||||
@@ -97,5 +95,5 @@ func DecodeMeta(r io.Reader) (Meta, error) {
|
||||
return &meta{format: imagetype.SVG, width: 1, height: 1}, nil
|
||||
}
|
||||
|
||||
return nil, ErrFormat
|
||||
return nil, newUnknownFormatError()
|
||||
}
|
||||
|
21
imagemeta/iptc/errors.go
Normal file
21
imagemeta/iptc/errors.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package iptc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type IptcError string
|
||||
|
||||
func newIptcError(format string, args ...interface{}) error {
|
||||
return ierrors.Wrap(
|
||||
IptcError(fmt.Sprintf(format, args...)),
|
||||
1,
|
||||
ierrors.WithStatusCode(422),
|
||||
ierrors.WithPublicMessage("Invalid IPTC data"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e IptcError) Error() string { return string(e) }
|
@@ -4,28 +4,22 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
var (
|
||||
iptcTagHeader = byte(0x1c)
|
||||
|
||||
errInvalidDataSize = errors.New("invalid IPTC data size")
|
||||
)
|
||||
var iptcTagHeader = byte(0x1c)
|
||||
|
||||
type IptcMap map[TagKey][]TagValue
|
||||
|
||||
func (m IptcMap) AddTag(key TagKey, data []byte) error {
|
||||
info, infoFound := tagInfoMap[key]
|
||||
if !infoFound {
|
||||
return fmt.Errorf("unknown tag %d:%d", key.RecordID, key.TagID)
|
||||
return newIptcError("unknown tag %d:%d", key.RecordID, key.TagID)
|
||||
}
|
||||
|
||||
dataSize := len(data)
|
||||
if dataSize < info.MinSize || dataSize > info.MaxSize {
|
||||
return fmt.Errorf("invalid tag data size. Min: %d, Max: %d, Has: %d", info.MinSize, info.MaxSize, dataSize)
|
||||
return newIptcError("invalid tag data size. Min: %d, Max: %d, Has: %d", info.MinSize, info.MaxSize, dataSize)
|
||||
}
|
||||
|
||||
value := TagValue{info.Format, data}
|
||||
@@ -89,17 +83,17 @@ func Parse(data []byte, m IptcMap) error {
|
||||
case 4:
|
||||
dataSize32 := uint32(0)
|
||||
if err := binary.Read(buf, binary.BigEndian, &dataSize32); err != nil {
|
||||
return fmt.Errorf("%s: %s", errInvalidDataSize, err)
|
||||
return newIptcError("invalid IPTC data size: %s", err)
|
||||
}
|
||||
dataSize = int(dataSize32)
|
||||
case 8:
|
||||
dataSize64 := uint64(0)
|
||||
if err := binary.Read(buf, binary.BigEndian, &dataSize64); err != nil {
|
||||
return fmt.Errorf("%s: %s", errInvalidDataSize, err)
|
||||
return newIptcError("invalid IPTC data size: %s", err)
|
||||
}
|
||||
dataSize = int(dataSize64)
|
||||
default:
|
||||
return errInvalidDataSize
|
||||
return newIptcError("invalid IPTC data size")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,6 @@ package iptc
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
@@ -425,7 +424,7 @@ var tagInfoMap = map[TagKey]TagInfo{
|
||||
func GetTagInfo(key TagKey) (TagInfo, error) {
|
||||
info, infoFound := tagInfoMap[key]
|
||||
if !infoFound {
|
||||
return TagInfo{}, fmt.Errorf("unknown tag %d:%d", key.RecordID, key.TagID)
|
||||
return TagInfo{}, newIptcError("unknown tag %d:%d", key.RecordID, key.TagID)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
@@ -42,10 +42,6 @@ func asJpegReader(r io.Reader) jpegReader {
|
||||
return bufio.NewReader(r)
|
||||
}
|
||||
|
||||
type JpegFormatError string
|
||||
|
||||
func (e JpegFormatError) Error() string { return "invalid JPEG format: " + string(e) }
|
||||
|
||||
func DecodeJpegMeta(rr io.Reader) (Meta, error) {
|
||||
var tmp [512]byte
|
||||
|
||||
@@ -55,7 +51,7 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) {
|
||||
return nil, err
|
||||
}
|
||||
if tmp[0] != 0xff || tmp[1] != jpegSoiMarker {
|
||||
return nil, JpegFormatError("missing SOI marker")
|
||||
return nil, newFormatError("JPEG", "missing SOI marker")
|
||||
}
|
||||
|
||||
for {
|
||||
@@ -89,11 +85,11 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) {
|
||||
}
|
||||
|
||||
if marker == jpegEoiMarker { // End Of Image.
|
||||
return nil, JpegFormatError("missing SOF marker")
|
||||
return nil, newFormatError("JPEG", "missing SOF marker")
|
||||
}
|
||||
|
||||
if marker == jpegSoiMarker {
|
||||
return nil, JpegFormatError("two SOI markers")
|
||||
return nil, newFormatError("JPEG", "two SOI markers")
|
||||
}
|
||||
|
||||
if jpegRst0Marker <= marker && marker <= jpegRst7Marker {
|
||||
@@ -118,7 +114,7 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) {
|
||||
}
|
||||
// We only support 8-bit precision.
|
||||
if tmp[0] != 8 {
|
||||
return nil, JpegFormatError("unsupported precision")
|
||||
return nil, newFormatError("JPEG", "unsupported precision")
|
||||
}
|
||||
|
||||
return &meta{
|
||||
@@ -128,7 +124,7 @@ func DecodeJpegMeta(rr io.Reader) (Meta, error) {
|
||||
}, nil
|
||||
|
||||
case jpegSosMarker:
|
||||
return nil, JpegFormatError("missing SOF marker")
|
||||
return nil, newFormatError("JPEG", "missing SOF marker")
|
||||
}
|
||||
|
||||
// Skip any other uninteresting segments
|
||||
|
@@ -54,13 +54,9 @@ func (br *jxlBitReader) Read(n uint64) (uint64, error) {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type JxlFormatError string
|
||||
|
||||
func (e JxlFormatError) Error() string { return "invalid JPEG XL format: " + string(e) }
|
||||
|
||||
func jxlReadJxlc(r io.Reader, boxDataSize uint64) ([]byte, error) {
|
||||
if boxDataSize < jxlCodestreamHeaderMinSize {
|
||||
return nil, JxlFormatError("invalid codestream box")
|
||||
return nil, newFormatError("JPEG XL", "invalid codestream box")
|
||||
}
|
||||
|
||||
toRead := boxDataSize
|
||||
@@ -73,7 +69,7 @@ func jxlReadJxlc(r io.Reader, boxDataSize uint64) ([]byte, error) {
|
||||
|
||||
func jxlReadJxlp(r io.Reader, boxDataSize uint64, codestream []byte) ([]byte, bool, error) {
|
||||
if boxDataSize < 4 {
|
||||
return nil, false, JxlFormatError("invalid jxlp box")
|
||||
return nil, false, newFormatError("JPEG XL", "invalid jxlp box")
|
||||
}
|
||||
|
||||
jxlpInd, err := heifReadN(r, 4)
|
||||
@@ -139,7 +135,7 @@ func jxlFindCodestream(r io.Reader) ([]byte, error) {
|
||||
}
|
||||
|
||||
if last {
|
||||
return nil, JxlFormatError("invalid codestream box")
|
||||
return nil, newFormatError("JPEG XL", "invalid codestream box")
|
||||
}
|
||||
|
||||
// Skip other boxes
|
||||
@@ -170,11 +166,11 @@ func jxlParseSize(br *jxlBitReader, small bool) (uint64, error) {
|
||||
|
||||
func jxlDecodeCodestreamHeader(buf []byte) (width, height uint64, err error) {
|
||||
if len(buf) < jxlCodestreamHeaderMinSize {
|
||||
return 0, 0, JxlFormatError("invalid codestream header")
|
||||
return 0, 0, newFormatError("JPEG XL", "invalid codestream header")
|
||||
}
|
||||
|
||||
if !bytes.Equal(buf[0:2], jxlCodestreamMarker) {
|
||||
return 0, 0, JxlFormatError("missing codestream marker")
|
||||
return 0, 0, newFormatError("JPEG XL", "missing codestream marker")
|
||||
}
|
||||
|
||||
br := NewJxlBitReader(buf[2:])
|
||||
@@ -230,7 +226,7 @@ func DecodeJxlMeta(r io.Reader) (Meta, error) {
|
||||
}
|
||||
|
||||
if !bytes.Equal(tmp[0:12], jxlISOBMFFMarker) {
|
||||
return nil, JxlFormatError("invalid header")
|
||||
return nil, newFormatError("JPEG XL", "invalid header")
|
||||
}
|
||||
|
||||
codestream, err = jxlFindCodestream(r)
|
||||
|
@@ -3,14 +3,11 @@ package photoshop
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ps3Header = []byte("Photoshop 3.0\x00")
|
||||
ps3BlockHeader = []byte("8BIM")
|
||||
|
||||
errInvalidPS3Header = errors.New("invalid Photoshop 3.0 header")
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -20,11 +17,11 @@ const (
|
||||
|
||||
type PhotoshopMap map[string][]byte
|
||||
|
||||
func Parse(data []byte, m PhotoshopMap) error {
|
||||
func Parse(data []byte, m PhotoshopMap) {
|
||||
buf := bytes.NewBuffer(data)
|
||||
|
||||
if !bytes.Equal(buf.Next(14), ps3Header) {
|
||||
return errInvalidPS3Header
|
||||
return
|
||||
}
|
||||
|
||||
// Read blocks
|
||||
@@ -58,8 +55,6 @@ func Parse(data []byte, m PhotoshopMap) error {
|
||||
|
||||
m[string(resoureceID)] = blockData
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m PhotoshopMap) Dump() []byte {
|
||||
|
@@ -10,10 +10,6 @@ import (
|
||||
|
||||
var pngMagick = []byte("\x89PNG\r\n\x1a\n")
|
||||
|
||||
type PngFormatError string
|
||||
|
||||
func (e PngFormatError) Error() string { return "invalid PNG format: " + string(e) }
|
||||
|
||||
func DecodePngMeta(r io.Reader) (Meta, error) {
|
||||
var tmp [16]byte
|
||||
|
||||
@@ -22,7 +18,7 @@ func DecodePngMeta(r io.Reader) (Meta, error) {
|
||||
}
|
||||
|
||||
if !bytes.Equal(pngMagick, tmp[:8]) {
|
||||
return nil, PngFormatError("not a PNG image")
|
||||
return nil, newFormatError("PNG", "not a PNG image")
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(r, tmp[:]); err != nil {
|
||||
|
@@ -35,10 +35,6 @@ func asTiffReader(r io.Reader) tiffReader {
|
||||
return bufio.NewReader(r)
|
||||
}
|
||||
|
||||
type TiffFormatError string
|
||||
|
||||
func (e TiffFormatError) Error() string { return "invalid TIFF format: " + string(e) }
|
||||
|
||||
func DecodeTiffMeta(rr io.Reader) (Meta, error) {
|
||||
var (
|
||||
tmp [12]byte
|
||||
@@ -57,7 +53,7 @@ func DecodeTiffMeta(rr io.Reader) (Meta, error) {
|
||||
case bytes.Equal(tiffBeHeader, tmp[0:4]):
|
||||
byteOrder = binary.BigEndian
|
||||
default:
|
||||
return nil, TiffFormatError("malformed header")
|
||||
return nil, newFormatError("TIFF", "malformed header")
|
||||
}
|
||||
|
||||
ifdOffset := int(byteOrder.Uint32(tmp[4:8]))
|
||||
@@ -96,7 +92,7 @@ func DecodeTiffMeta(rr io.Reader) (Meta, error) {
|
||||
case tiffDtLong:
|
||||
value = int(byteOrder.Uint32(tmp[8:12]))
|
||||
default:
|
||||
return nil, TiffFormatError("unsupported IFD entry datatype")
|
||||
return nil, newFormatError("TIFF", "unsupported IFD entry datatype")
|
||||
}
|
||||
|
||||
if tag == tiffImageWidth {
|
||||
@@ -114,7 +110,7 @@ func DecodeTiffMeta(rr io.Reader) (Meta, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return nil, TiffFormatError("image dimensions are not specified")
|
||||
return nil, newFormatError("TIFF", "image dimensions are not specified")
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@@ -7,7 +7,6 @@
|
||||
package imagemeta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
@@ -16,8 +15,6 @@ import (
|
||||
"golang.org/x/image/vp8l"
|
||||
)
|
||||
|
||||
var ErrWebpInvalidFormat = errors.New("webp: invalid format")
|
||||
|
||||
var (
|
||||
webpFccALPH = riff.FourCC{'A', 'L', 'P', 'H'}
|
||||
webpFccVP8 = riff.FourCC{'V', 'P', '8', ' '}
|
||||
@@ -32,7 +29,7 @@ func DecodeWebpMeta(r io.Reader) (Meta, error) {
|
||||
return nil, err
|
||||
}
|
||||
if formType != webpFccWEBP {
|
||||
return nil, ErrWebpInvalidFormat
|
||||
return nil, newFormatError("WEBP", "invalid form type")
|
||||
}
|
||||
|
||||
var buf [10]byte
|
||||
@@ -40,7 +37,7 @@ func DecodeWebpMeta(r io.Reader) (Meta, error) {
|
||||
for {
|
||||
chunkID, chunkLen, chunkData, err := riffReader.Next()
|
||||
if err == io.EOF {
|
||||
err = ErrWebpInvalidFormat
|
||||
err = newFormatError("WEBP", "no VP8, VP8L or VP8X chunk found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,7 +48,7 @@ func DecodeWebpMeta(r io.Reader) (Meta, error) {
|
||||
// Ignore
|
||||
case webpFccVP8:
|
||||
if int32(chunkLen) < 0 {
|
||||
return nil, ErrWebpInvalidFormat
|
||||
return nil, newFormatError("WEBP", "invalid chunk length")
|
||||
}
|
||||
|
||||
d := vp8.NewDecoder()
|
||||
@@ -79,7 +76,7 @@ func DecodeWebpMeta(r io.Reader) (Meta, error) {
|
||||
|
||||
case webpFccVP8X:
|
||||
if chunkLen != 10 {
|
||||
return nil, ErrWebpInvalidFormat
|
||||
return nil, newFormatError("WEBP", "invalid chunk length")
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(chunkData, buf[:10]); err != nil {
|
||||
@@ -96,7 +93,7 @@ func DecodeWebpMeta(r io.Reader) (Meta, error) {
|
||||
}, nil
|
||||
|
||||
default:
|
||||
return nil, ErrWebpInvalidFormat
|
||||
return nil, newFormatError("WEBP", "unknown chunk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
options/errors.go
Normal file
50
options/errors.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type (
|
||||
InvalidURLError string
|
||||
UnknownOptionError string
|
||||
OptionArgumentError string
|
||||
)
|
||||
|
||||
func newInvalidURLError(format string, args ...interface{}) error {
|
||||
return ierrors.Wrap(
|
||||
InvalidURLError(fmt.Sprintf(format, args...)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Invalid URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e InvalidURLError) Error() string { return string(e) }
|
||||
|
||||
func newUnknownOptionError(kind, opt string) error {
|
||||
return ierrors.Wrap(
|
||||
UnknownOptionError(fmt.Sprintf("Unknown %s option %s", kind, opt)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Invalid URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e UnknownOptionError) Error() string { return string(e) }
|
||||
|
||||
func newOptionArgumentError(format string, args ...interface{}) error {
|
||||
return ierrors.Wrap(
|
||||
OptionArgumentError(fmt.Sprintf(format, args...)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Invalid URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e OptionArgumentError) Error() string { return string(e) }
|
@@ -2,8 +2,6 @@ package options
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
@@ -23,8 +21,6 @@ import (
|
||||
|
||||
const maxClientHintDPR = 8
|
||||
|
||||
var errExpiredURL = errors.New("Expired URL")
|
||||
|
||||
type ExtendOptions struct {
|
||||
Enabled bool
|
||||
Gravity GravityOptions
|
||||
@@ -199,7 +195,7 @@ func parseDimension(d *int, name, arg string) error {
|
||||
if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
|
||||
*d = v
|
||||
} else {
|
||||
return fmt.Errorf("Invalid %s: %s", name, arg)
|
||||
return newOptionArgumentError("Invalid %s: %s", name, arg)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -225,32 +221,32 @@ func parseGravity(g *GravityOptions, name string, args []string, allowedTypes []
|
||||
if t, ok := gravityTypes[args[0]]; ok && slices.Contains(allowedTypes, t) {
|
||||
g.Type = t
|
||||
} else {
|
||||
return fmt.Errorf("Invalid %s: %s", name, args[0])
|
||||
return newOptionArgumentError("Invalid %s: %s", name, args[0])
|
||||
}
|
||||
|
||||
switch g.Type {
|
||||
case GravitySmart:
|
||||
if nArgs > 1 {
|
||||
return fmt.Errorf("Invalid %s arguments: %v", name, args)
|
||||
return newOptionArgumentError("Invalid %s arguments: %v", name, args)
|
||||
}
|
||||
g.X, g.Y = 0.0, 0.0
|
||||
|
||||
case GravityFocusPoint:
|
||||
if nArgs != 3 {
|
||||
return fmt.Errorf("Invalid %s arguments: %v", name, args)
|
||||
return newOptionArgumentError("Invalid %s arguments: %v", name, args)
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
if nArgs > 3 {
|
||||
return fmt.Errorf("Invalid %s arguments: %v", name, args)
|
||||
return newOptionArgumentError("Invalid %s arguments: %v", name, args)
|
||||
}
|
||||
|
||||
if nArgs > 1 {
|
||||
if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
|
||||
g.X = x
|
||||
} else {
|
||||
return fmt.Errorf("Invalid %s X: %s", name, args[1])
|
||||
return newOptionArgumentError("Invalid %s X: %s", name, args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +254,7 @@ func parseGravity(g *GravityOptions, name string, args []string, allowedTypes []
|
||||
if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
|
||||
g.Y = y
|
||||
} else {
|
||||
return fmt.Errorf("Invalid %s Y: %s", name, args[2])
|
||||
return newOptionArgumentError("Invalid %s Y: %s", name, args[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -268,7 +264,7 @@ func parseGravity(g *GravityOptions, name string, args []string, allowedTypes []
|
||||
|
||||
func parseExtend(opts *ExtendOptions, name string, args []string) error {
|
||||
if len(args) > 4 {
|
||||
return fmt.Errorf("Invalid %s arguments: %v", name, args)
|
||||
return newOptionArgumentError("Invalid %s arguments: %v", name, args)
|
||||
}
|
||||
|
||||
opts.Enabled = parseBoolOption(args[0])
|
||||
@@ -282,7 +278,7 @@ func parseExtend(opts *ExtendOptions, name string, args []string) error {
|
||||
|
||||
func applyWidthOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid width arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid width arguments: %v", args)
|
||||
}
|
||||
|
||||
return parseDimension(&po.Width, "width", args[0])
|
||||
@@ -290,7 +286,7 @@ func applyWidthOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyHeightOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid height arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid height arguments: %v", args)
|
||||
}
|
||||
|
||||
return parseDimension(&po.Height, "height", args[0])
|
||||
@@ -298,7 +294,7 @@ func applyHeightOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyMinWidthOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid min width arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid min width arguments: %v", args)
|
||||
}
|
||||
|
||||
return parseDimension(&po.MinWidth, "min width", args[0])
|
||||
@@ -306,7 +302,7 @@ func applyMinWidthOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyMinHeightOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid min height arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid min height arguments: %v", args)
|
||||
}
|
||||
|
||||
return parseDimension(&po.MinHeight, " min height", args[0])
|
||||
@@ -314,7 +310,7 @@ func applyMinHeightOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyEnlargeOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid enlarge arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid enlarge arguments: %v", args)
|
||||
}
|
||||
|
||||
po.Enlarge = parseBoolOption(args[0])
|
||||
@@ -332,7 +328,7 @@ func applyExtendAspectRatioOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applySizeOption(po *ProcessingOptions, args []string) (err error) {
|
||||
if len(args) > 7 {
|
||||
return fmt.Errorf("Invalid size arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid size arguments: %v", args)
|
||||
}
|
||||
|
||||
if len(args) >= 1 && len(args[0]) > 0 {
|
||||
@@ -364,13 +360,13 @@ func applySizeOption(po *ProcessingOptions, args []string) (err error) {
|
||||
|
||||
func applyResizingTypeOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid resizing type arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid resizing type arguments: %v", args)
|
||||
}
|
||||
|
||||
if r, ok := resizeTypes[args[0]]; ok {
|
||||
po.ResizingType = r
|
||||
} else {
|
||||
return fmt.Errorf("Invalid resize type: %s", args[0])
|
||||
return newOptionArgumentError("Invalid resize type: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -378,7 +374,7 @@ func applyResizingTypeOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyResizeOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 8 {
|
||||
return fmt.Errorf("Invalid resize arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid resize arguments: %v", args)
|
||||
}
|
||||
|
||||
if len(args[0]) > 0 {
|
||||
@@ -400,21 +396,21 @@ func applyZoomOption(po *ProcessingOptions, args []string) error {
|
||||
nArgs := len(args)
|
||||
|
||||
if nArgs > 2 {
|
||||
return fmt.Errorf("Invalid zoom arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid zoom arguments: %v", args)
|
||||
}
|
||||
|
||||
if z, err := strconv.ParseFloat(args[0], 64); err == nil && z > 0 {
|
||||
po.ZoomWidth = z
|
||||
po.ZoomHeight = z
|
||||
} else {
|
||||
return fmt.Errorf("Invalid zoom value: %s", args[0])
|
||||
return newOptionArgumentError("Invalid zoom value: %s", args[0])
|
||||
}
|
||||
|
||||
if nArgs > 1 {
|
||||
if z, err := strconv.ParseFloat(args[1], 64); err == nil && z > 0 {
|
||||
po.ZoomHeight = z
|
||||
} else {
|
||||
return fmt.Errorf("Invalid zoom value: %s", args[0])
|
||||
return newOptionArgumentError("Invalid zoom value: %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,13 +419,13 @@ func applyZoomOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyDprOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid dpr arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid dpr arguments: %v", args)
|
||||
}
|
||||
|
||||
if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
|
||||
po.Dpr = d
|
||||
} else {
|
||||
return fmt.Errorf("Invalid dpr: %s", args[0])
|
||||
return newOptionArgumentError("Invalid dpr: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -443,14 +439,14 @@ func applyCropOption(po *ProcessingOptions, args []string) error {
|
||||
if w, err := strconv.ParseFloat(args[0], 64); err == nil && w >= 0 {
|
||||
po.Crop.Width = w
|
||||
} else {
|
||||
return fmt.Errorf("Invalid crop width: %s", args[0])
|
||||
return newOptionArgumentError("Invalid crop width: %s", args[0])
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
if h, err := strconv.ParseFloat(args[1], 64); err == nil && h >= 0 {
|
||||
po.Crop.Height = h
|
||||
} else {
|
||||
return fmt.Errorf("Invalid crop height: %s", args[1])
|
||||
return newOptionArgumentError("Invalid crop height: %s", args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,7 +461,7 @@ func applyPaddingOption(po *ProcessingOptions, args []string) error {
|
||||
nArgs := len(args)
|
||||
|
||||
if nArgs < 1 || nArgs > 4 {
|
||||
return fmt.Errorf("Invalid padding arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid padding arguments: %v", args)
|
||||
}
|
||||
|
||||
po.Padding.Enabled = true
|
||||
@@ -509,14 +505,14 @@ func applyTrimOption(po *ProcessingOptions, args []string) error {
|
||||
nArgs := len(args)
|
||||
|
||||
if nArgs > 4 {
|
||||
return fmt.Errorf("Invalid trim arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid trim arguments: %v", args)
|
||||
}
|
||||
|
||||
if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
|
||||
po.Trim.Enabled = true
|
||||
po.Trim.Threshold = t
|
||||
} else {
|
||||
return fmt.Errorf("Invalid trim threshold: %s", args[0])
|
||||
return newOptionArgumentError("Invalid trim threshold: %s", args[0])
|
||||
}
|
||||
|
||||
if nArgs > 1 && len(args[1]) > 0 {
|
||||
@@ -524,7 +520,7 @@ func applyTrimOption(po *ProcessingOptions, args []string) error {
|
||||
po.Trim.Color = c
|
||||
po.Trim.Smart = false
|
||||
} else {
|
||||
return fmt.Errorf("Invalid trim color: %s", args[1])
|
||||
return newOptionArgumentError("Invalid trim color: %s", args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,13 +537,13 @@ func applyTrimOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyRotateOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid rotate arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid rotate arguments: %v", args)
|
||||
}
|
||||
|
||||
if r, err := strconv.Atoi(args[0]); err == nil && r%90 == 0 {
|
||||
po.Rotate = r
|
||||
} else {
|
||||
return fmt.Errorf("Invalid rotation angle: %s", args[0])
|
||||
return newOptionArgumentError("Invalid rotation angle: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -555,13 +551,13 @@ func applyRotateOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyQualityOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid quality arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid quality arguments: %v", args)
|
||||
}
|
||||
|
||||
if q, err := strconv.Atoi(args[0]); err == nil && q >= 0 && q <= 100 {
|
||||
po.Quality = q
|
||||
} else {
|
||||
return fmt.Errorf("Invalid quality: %s", args[0])
|
||||
return newOptionArgumentError("Invalid quality: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -570,19 +566,19 @@ func applyQualityOption(po *ProcessingOptions, args []string) error {
|
||||
func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
|
||||
argsLen := len(args)
|
||||
if len(args)%2 != 0 {
|
||||
return fmt.Errorf("Missing quality for: %s", args[argsLen-1])
|
||||
return newOptionArgumentError("Missing quality for: %s", args[argsLen-1])
|
||||
}
|
||||
|
||||
for i := 0; i < argsLen; i += 2 {
|
||||
f, ok := imagetype.Types[args[i]]
|
||||
if !ok {
|
||||
return fmt.Errorf("Invalid image format: %s", args[i])
|
||||
return newOptionArgumentError("Invalid image format: %s", args[i])
|
||||
}
|
||||
|
||||
if q, err := strconv.Atoi(args[i+1]); err == nil && q >= 0 && q <= 100 {
|
||||
po.FormatQuality[f] = q
|
||||
} else {
|
||||
return fmt.Errorf("Invalid quality for %s: %s", args[i], args[i+1])
|
||||
return newOptionArgumentError("Invalid quality for %s: %s", args[i], args[i+1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,13 +587,13 @@ func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyMaxBytesOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid max_bytes arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid max_bytes arguments: %v", args)
|
||||
}
|
||||
|
||||
if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
|
||||
po.MaxBytes = max
|
||||
} else {
|
||||
return fmt.Errorf("Invalid max_bytes: %s", args[0])
|
||||
return newOptionArgumentError("Invalid max_bytes: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -612,7 +608,7 @@ func applyBackgroundOption(po *ProcessingOptions, args []string) error {
|
||||
po.Flatten = true
|
||||
po.Background = c
|
||||
} else {
|
||||
return fmt.Errorf("Invalid background argument: %s", err)
|
||||
return newOptionArgumentError("Invalid background argument: %s", err)
|
||||
}
|
||||
|
||||
case 3:
|
||||
@@ -621,23 +617,23 @@ func applyBackgroundOption(po *ProcessingOptions, args []string) error {
|
||||
if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
|
||||
po.Background.R = uint8(r)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid background red channel: %s", args[0])
|
||||
return newOptionArgumentError("Invalid background red channel: %s", args[0])
|
||||
}
|
||||
|
||||
if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
|
||||
po.Background.G = uint8(g)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid background green channel: %s", args[1])
|
||||
return newOptionArgumentError("Invalid background green channel: %s", args[1])
|
||||
}
|
||||
|
||||
if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
|
||||
po.Background.B = uint8(b)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid background blue channel: %s", args[2])
|
||||
return newOptionArgumentError("Invalid background blue channel: %s", args[2])
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Invalid background arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid background arguments: %v", args)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -645,13 +641,13 @@ func applyBackgroundOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyBlurOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid blur arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid blur arguments: %v", args)
|
||||
}
|
||||
|
||||
if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
|
||||
po.Blur = float32(b)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid blur: %s", args[0])
|
||||
return newOptionArgumentError("Invalid blur: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -659,13 +655,13 @@ func applyBlurOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applySharpenOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid sharpen arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid sharpen arguments: %v", args)
|
||||
}
|
||||
|
||||
if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
|
||||
po.Sharpen = float32(s)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid sharpen: %s", args[0])
|
||||
return newOptionArgumentError("Invalid sharpen: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -673,13 +669,13 @@ func applySharpenOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyPixelateOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid pixelate arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid pixelate arguments: %v", args)
|
||||
}
|
||||
|
||||
if p, err := strconv.Atoi(args[0]); err == nil && p >= 0 {
|
||||
po.Pixelate = p
|
||||
} else {
|
||||
return fmt.Errorf("Invalid pixelate: %s", args[0])
|
||||
return newOptionArgumentError("Invalid pixelate: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -699,7 +695,7 @@ func applyPresetOption(po *ProcessingOptions, args []string, usedPresets ...stri
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Unknown preset: %s", preset)
|
||||
return newOptionArgumentError("Unknown preset: %s", preset)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,21 +704,21 @@ func applyPresetOption(po *ProcessingOptions, args []string, usedPresets ...stri
|
||||
|
||||
func applyWatermarkOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 7 {
|
||||
return fmt.Errorf("Invalid watermark arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid watermark arguments: %v", args)
|
||||
}
|
||||
|
||||
if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
|
||||
po.Watermark.Enabled = o > 0
|
||||
po.Watermark.Opacity = o
|
||||
} else {
|
||||
return fmt.Errorf("Invalid watermark opacity: %s", args[0])
|
||||
return newOptionArgumentError("Invalid watermark opacity: %s", args[0])
|
||||
}
|
||||
|
||||
if len(args) > 1 && len(args[1]) > 0 {
|
||||
if g, ok := gravityTypes[args[1]]; ok && slices.Contains(watermarkGravityTypes, g) {
|
||||
po.Watermark.Position.Type = g
|
||||
} else {
|
||||
return fmt.Errorf("Invalid watermark position: %s", args[1])
|
||||
return newOptionArgumentError("Invalid watermark position: %s", args[1])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +726,7 @@ func applyWatermarkOption(po *ProcessingOptions, args []string) error {
|
||||
if x, err := strconv.ParseFloat(args[2], 64); err == nil {
|
||||
po.Watermark.Position.X = x
|
||||
} else {
|
||||
return fmt.Errorf("Invalid watermark X offset: %s", args[2])
|
||||
return newOptionArgumentError("Invalid watermark X offset: %s", args[2])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,7 +734,7 @@ func applyWatermarkOption(po *ProcessingOptions, args []string) error {
|
||||
if y, err := strconv.ParseFloat(args[3], 64); err == nil {
|
||||
po.Watermark.Position.Y = y
|
||||
} else {
|
||||
return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
|
||||
return newOptionArgumentError("Invalid watermark Y offset: %s", args[3])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,7 +742,7 @@ func applyWatermarkOption(po *ProcessingOptions, args []string) error {
|
||||
if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
|
||||
po.Watermark.Scale = s
|
||||
} else {
|
||||
return fmt.Errorf("Invalid watermark scale: %s", args[4])
|
||||
return newOptionArgumentError("Invalid watermark scale: %s", args[4])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,13 +751,13 @@ func applyWatermarkOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyFormatOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid format arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid format arguments: %v", args)
|
||||
}
|
||||
|
||||
if f, ok := imagetype.Types[args[0]]; ok {
|
||||
po.Format = f
|
||||
} else {
|
||||
return fmt.Errorf("Invalid image format: %s", args[0])
|
||||
return newOptionArgumentError("Invalid image format: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -769,7 +765,7 @@ func applyFormatOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyCacheBusterOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid cache buster arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid cache buster arguments: %v", args)
|
||||
}
|
||||
|
||||
po.CacheBuster = args[0]
|
||||
@@ -782,7 +778,7 @@ func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) erro
|
||||
if f, ok := imagetype.Types[format]; ok {
|
||||
po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid image format in skip processing: %s", format)
|
||||
return newOptionArgumentError("Invalid image format in skip processing: %s", format)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -791,7 +787,7 @@ func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) erro
|
||||
|
||||
func applyRawOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid return_attachment arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid return_attachment arguments: %v", args)
|
||||
}
|
||||
|
||||
po.Raw = parseBoolOption(args[0])
|
||||
@@ -801,7 +797,7 @@ func applyRawOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyFilenameOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 2 {
|
||||
return fmt.Errorf("Invalid filename arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid filename arguments: %v", args)
|
||||
}
|
||||
|
||||
po.Filename = args[0]
|
||||
@@ -809,7 +805,7 @@ func applyFilenameOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 && parseBoolOption(args[1]) {
|
||||
decoded, err := base64.RawURLEncoding.DecodeString(po.Filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid filename encoding: %s", err)
|
||||
return newOptionArgumentError("Invalid filename encoding: %s", err)
|
||||
}
|
||||
|
||||
po.Filename = string(decoded)
|
||||
@@ -820,16 +816,16 @@ func applyFilenameOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyExpiresOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid expires arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid expires arguments: %v", args)
|
||||
}
|
||||
|
||||
timestamp, err := strconv.ParseInt(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid expires argument: %v", args[0])
|
||||
return newOptionArgumentError("Invalid expires argument: %v", args[0])
|
||||
}
|
||||
|
||||
if timestamp > 0 && timestamp < time.Now().Unix() {
|
||||
return errExpiredURL
|
||||
return newOptionArgumentError("Expired URL")
|
||||
}
|
||||
|
||||
expires := time.Unix(timestamp, 0)
|
||||
@@ -840,7 +836,7 @@ func applyExpiresOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyStripMetadataOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid strip metadata arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid strip metadata arguments: %v", args)
|
||||
}
|
||||
|
||||
po.StripMetadata = parseBoolOption(args[0])
|
||||
@@ -850,7 +846,7 @@ func applyStripMetadataOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyKeepCopyrightOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid keep copyright arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid keep copyright arguments: %v", args)
|
||||
}
|
||||
|
||||
po.KeepCopyright = parseBoolOption(args[0])
|
||||
@@ -860,7 +856,7 @@ func applyKeepCopyrightOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyStripColorProfileOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid strip color profile arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid strip color profile arguments: %v", args)
|
||||
}
|
||||
|
||||
po.StripColorProfile = parseBoolOption(args[0])
|
||||
@@ -870,7 +866,7 @@ func applyStripColorProfileOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyAutoRotateOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid auto rotate arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid auto rotate arguments: %v", args)
|
||||
}
|
||||
|
||||
po.AutoRotate = parseBoolOption(args[0])
|
||||
@@ -880,7 +876,7 @@ func applyAutoRotateOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyEnforceThumbnailOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid enforce thumbnail arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid enforce thumbnail arguments: %v", args)
|
||||
}
|
||||
|
||||
po.EnforceThumbnail = parseBoolOption(args[0])
|
||||
@@ -890,7 +886,7 @@ func applyEnforceThumbnailOption(po *ProcessingOptions, args []string) error {
|
||||
|
||||
func applyReturnAttachmentOption(po *ProcessingOptions, args []string) error {
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid return_attachment arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid return_attachment arguments: %v", args)
|
||||
}
|
||||
|
||||
po.ReturnAttachment = parseBoolOption(args[0])
|
||||
@@ -904,13 +900,13 @@ func applyMaxSrcResolutionOption(po *ProcessingOptions, args []string) error {
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid max_src_resolution arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid max_src_resolution arguments: %v", args)
|
||||
}
|
||||
|
||||
if x, err := strconv.ParseFloat(args[0], 64); err == nil && x > 0 {
|
||||
po.SecurityOptions.MaxSrcResolution = int(x * 1000000)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid max_src_resolution: %s", args[0])
|
||||
return newOptionArgumentError("Invalid max_src_resolution: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -922,13 +918,13 @@ func applyMaxSrcFileSizeOption(po *ProcessingOptions, args []string) error {
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid max_src_file_size arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid max_src_file_size arguments: %v", args)
|
||||
}
|
||||
|
||||
if x, err := strconv.Atoi(args[0]); err == nil {
|
||||
po.SecurityOptions.MaxSrcFileSize = x
|
||||
} else {
|
||||
return fmt.Errorf("Invalid max_src_file_size: %s", args[0])
|
||||
return newOptionArgumentError("Invalid max_src_file_size: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -940,13 +936,13 @@ func applyMaxAnimationFramesOption(po *ProcessingOptions, args []string) error {
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid max_animation_frames arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid max_animation_frames arguments: %v", args)
|
||||
}
|
||||
|
||||
if x, err := strconv.Atoi(args[0]); err == nil && x > 0 {
|
||||
po.SecurityOptions.MaxAnimationFrames = x
|
||||
} else {
|
||||
return fmt.Errorf("Invalid max_animation_frames: %s", args[0])
|
||||
return newOptionArgumentError("Invalid max_animation_frames: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -958,13 +954,13 @@ func applyMaxAnimationFrameResolutionOption(po *ProcessingOptions, args []string
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
return fmt.Errorf("Invalid max_animation_frame_resolution arguments: %v", args)
|
||||
return newOptionArgumentError("Invalid max_animation_frame_resolution arguments: %v", args)
|
||||
}
|
||||
|
||||
if x, err := strconv.ParseFloat(args[0], 64); err == nil {
|
||||
po.SecurityOptions.MaxAnimationFrameResolution = int(x * 1000000)
|
||||
} else {
|
||||
return fmt.Errorf("Invalid max_animation_frame_resolution: %s", args[0])
|
||||
return newOptionArgumentError("Invalid max_animation_frame_resolution: %s", args[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -1062,7 +1058,7 @@ func applyURLOption(po *ProcessingOptions, name string, args []string, usedPrese
|
||||
return applyMaxAnimationFrameResolutionOption(po, args)
|
||||
}
|
||||
|
||||
return fmt.Errorf("Unknown processing option: %s", name)
|
||||
return newUnknownOptionError("processing", name)
|
||||
}
|
||||
|
||||
func applyURLOptions(po *ProcessingOptions, options urlOptions, usedPresets ...string) error {
|
||||
@@ -1128,11 +1124,7 @@ func defaultProcessingOptions(headers http.Header) (*ProcessingOptions, error) {
|
||||
|
||||
func parsePathOptions(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
|
||||
if _, ok := resizeTypes[parts[0]]; ok {
|
||||
return nil, "", ierrors.New(
|
||||
404,
|
||||
"It looks like you're using the deprecated basic URL format",
|
||||
"Invalid URL",
|
||||
)
|
||||
return nil, "", newInvalidURLError("It looks like you're using the deprecated basic URL format")
|
||||
}
|
||||
|
||||
po, err := defaultProcessingOptions(headers)
|
||||
@@ -1189,7 +1181,7 @@ func parsePathPresets(parts []string, headers http.Header) (*ProcessingOptions,
|
||||
|
||||
func ParsePath(path string, headers http.Header) (*ProcessingOptions, string, error) {
|
||||
if path == "" || path == "/" {
|
||||
return nil, "", ierrors.New(404, fmt.Sprintf("Invalid path: %s", path), "Invalid URL")
|
||||
return nil, "", newInvalidURLError("Invalid path: %s", path)
|
||||
}
|
||||
|
||||
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
|
||||
@@ -1207,7 +1199,7 @@ func ParsePath(path string, headers http.Header) (*ProcessingOptions, string, er
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, "", ierrors.New(404, err.Error(), "Invalid URL")
|
||||
return nil, "", ierrors.Wrap(err, 0)
|
||||
}
|
||||
|
||||
return po, imageURL, nil
|
||||
|
@@ -601,8 +601,7 @@ func (s *ProcessingOptionsTestSuite) TestParseExpiresExpired() {
|
||||
path := "/exp:1609448400/plain/http://images.dev/lorem/ipsum.jpg"
|
||||
_, _, err := ParsePath(path, make(http.Header))
|
||||
|
||||
s.Require().Error(err)
|
||||
s.Require().Equal(errExpiredURL.Error(), err.Error())
|
||||
s.Require().Error(err, "Expired URL")
|
||||
}
|
||||
|
||||
func (s *ProcessingOptionsTestSuite) TestParsePathOnlyPresets() {
|
||||
|
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
@@ -147,8 +148,7 @@ func respondWithImage(reqID string, r *http.Request, rw http.ResponseWriter, sta
|
||||
|
||||
var ierr *ierrors.Error
|
||||
if err != nil {
|
||||
ierr = ierrors.New(statusCode, fmt.Sprintf("Failed to write response: %s", err), "Failed to write response")
|
||||
ierr.Unexpected = true
|
||||
ierr = newResponseWriteError(err)
|
||||
|
||||
if config.ReportIOErrors {
|
||||
sendErr(r.Context(), "IO", ierr)
|
||||
@@ -183,7 +183,7 @@ func sendErr(ctx context.Context, errType string, err error) {
|
||||
send := true
|
||||
|
||||
if ierr, ok := err.(*ierrors.Error); ok {
|
||||
switch ierr.StatusCode {
|
||||
switch ierr.StatusCode() {
|
||||
case http.StatusServiceUnavailable:
|
||||
errType = "timeout"
|
||||
case 499:
|
||||
@@ -231,15 +231,15 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
signature = path[:signatureEnd]
|
||||
path = path[signatureEnd:]
|
||||
} else {
|
||||
sendErrAndPanic(ctx, "path_parsing", ierrors.New(
|
||||
404, fmt.Sprintf("Invalid path: %s", path), "Invalid URL",
|
||||
))
|
||||
sendErrAndPanic(ctx, "path_parsing", newInvalidURLErrorf(
|
||||
http.StatusNotFound, "Invalid path: %s", path),
|
||||
)
|
||||
}
|
||||
|
||||
path = fixPath(path)
|
||||
|
||||
if err := security.VerifySignature(signature, path); err != nil {
|
||||
sendErrAndPanic(ctx, "security", ierrors.New(403, err.Error(), "Forbidden"))
|
||||
sendErrAndPanic(ctx, "security", err)
|
||||
}
|
||||
|
||||
po, imageURL, err := options.ParsePath(path, r.Header)
|
||||
@@ -261,10 +261,9 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// SVG is a special case. Though saving to svg is not supported, SVG->SVG is.
|
||||
if !vips.SupportsSave(po.Format) && po.Format != imagetype.Unknown && po.Format != imagetype.SVG {
|
||||
sendErrAndPanic(ctx, "path_parsing", ierrors.New(
|
||||
422,
|
||||
fmt.Sprintf("Resulting image format is not supported: %s", po.Format),
|
||||
"Invalid URL",
|
||||
sendErrAndPanic(ctx, "path_parsing", newInvalidURLErrorf(
|
||||
http.StatusUnprocessableEntity,
|
||||
"Resulting image format is not supported: %s", po.Format,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -291,7 +290,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
if queueSem != nil {
|
||||
acquired := queueSem.TryAcquire(1)
|
||||
if !acquired {
|
||||
panic(ierrors.New(429, "Too many requests", "Too many requests"))
|
||||
panic(newTooManyRequestsError())
|
||||
}
|
||||
defer queueSem.Release(1)
|
||||
}
|
||||
@@ -334,21 +333,28 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
return imagedata.Download(ctx, imageURL, "source image", downloadOpts, po.SecurityOptions)
|
||||
}()
|
||||
|
||||
if err == nil {
|
||||
var nmErr imagedata.NotModifiedError
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
defer originData.Close()
|
||||
} else if nmErr, ok := err.(*imagedata.ErrorNotModified); ok {
|
||||
|
||||
case errors.As(err, &nmErr):
|
||||
if config.ETagEnabled && len(etagHandler.ImageEtagExpected()) != 0 {
|
||||
rw.Header().Set("ETag", etagHandler.GenerateExpectedETag())
|
||||
}
|
||||
respondWithNotModified(reqID, r, rw, po, imageURL, nmErr.Headers)
|
||||
respondWithNotModified(reqID, r, rw, po, imageURL, nmErr.Headers())
|
||||
return
|
||||
} else {
|
||||
|
||||
default:
|
||||
// This may be a request timeout error or a request cancelled error.
|
||||
// Check it before moving further
|
||||
checkErr(ctx, "timeout", router.CheckTimeout(ctx))
|
||||
|
||||
ierr := ierrors.Wrap(err, 0)
|
||||
ierr.Unexpected = ierr.Unexpected || config.ReportDownloadingErrors
|
||||
if config.ReportDownloadingErrors {
|
||||
ierr = ierrors.Wrap(ierr, 0, ierrors.WithShouldReport(true))
|
||||
}
|
||||
|
||||
sendErr(ctx, "download", ierr)
|
||||
|
||||
@@ -358,7 +364,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// We didn't panic, so the error is not reported.
|
||||
// Report it now
|
||||
if ierr.Unexpected {
|
||||
if ierr.ShouldReport() {
|
||||
errorreport.Report(ierr, r)
|
||||
}
|
||||
|
||||
@@ -367,7 +373,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
if config.FallbackImageHTTPCode > 0 {
|
||||
statusCode = config.FallbackImageHTTPCode
|
||||
} else {
|
||||
statusCode = ierr.StatusCode
|
||||
statusCode = ierr.StatusCode()
|
||||
}
|
||||
|
||||
originData = imagedata.FallbackImage
|
||||
@@ -413,17 +419,17 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !vips.SupportsLoad(originData.Type) {
|
||||
sendErrAndPanic(ctx, "processing", ierrors.New(
|
||||
422,
|
||||
fmt.Sprintf("Source image format is not supported: %s", originData.Type),
|
||||
"Invalid URL",
|
||||
sendErrAndPanic(ctx, "processing", newInvalidURLErrorf(
|
||||
http.StatusUnprocessableEntity,
|
||||
"Source image format is not supported: %s", originData.Type,
|
||||
))
|
||||
}
|
||||
|
||||
// At this point we can't allow requested format to be SVG as we can't save SVGs
|
||||
if po.Format == imagetype.SVG {
|
||||
sendErrAndPanic(ctx, "processing", ierrors.New(
|
||||
422, "Resulting image format is not supported: svg", "Invalid URL",
|
||||
sendErrAndPanic(ctx, "processing", newInvalidURLErrorf(
|
||||
http.StatusUnprocessableEntity,
|
||||
"Resulting image format is not supported: svg",
|
||||
))
|
||||
}
|
||||
|
||||
|
@@ -225,7 +225,6 @@ func (s *ProcessingHandlerTestSuite) TestSourceNetworkValidation() {
|
||||
var rw *httptest.ResponseRecorder
|
||||
|
||||
u := fmt.Sprintf("/unsafe/rs:fill:4:4/plain/%s/test1.png", server.URL)
|
||||
fmt.Println(u)
|
||||
|
||||
rw = s.send(u)
|
||||
s.Require().Equal(200, rw.Result().StatusCode)
|
||||
|
51
router/errors.go
Normal file
51
router/errors.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type (
|
||||
RouteNotDefinedError string
|
||||
RequestCancelledError string
|
||||
RequestTimeoutError string
|
||||
)
|
||||
|
||||
func newRouteNotDefinedError(path string) *ierrors.Error {
|
||||
return ierrors.Wrap(
|
||||
RouteNotDefinedError(fmt.Sprintf("Route for %s is not defined", path)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Not found"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e RouteNotDefinedError) Error() string { return string(e) }
|
||||
|
||||
func newRequestCancelledError(after time.Duration) *ierrors.Error {
|
||||
return ierrors.Wrap(
|
||||
RequestCancelledError(fmt.Sprintf("Request was cancelled after %v", after)),
|
||||
1,
|
||||
ierrors.WithStatusCode(499),
|
||||
ierrors.WithPublicMessage("Cancelled"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e RequestCancelledError) Error() string { return string(e) }
|
||||
|
||||
func newRequestTimeoutError(after time.Duration) *ierrors.Error {
|
||||
return ierrors.Wrap(
|
||||
RequestTimeoutError(fmt.Sprintf("Request was timed out after %v", after)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusServiceUnavailable),
|
||||
ierrors.WithPublicMessage("Gateway Timeout"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e RequestTimeoutError) Error() string { return string(e) }
|
@@ -24,7 +24,7 @@ func LogResponse(reqID string, r *http.Request, status int, err *ierrors.Error,
|
||||
var level log.Level
|
||||
|
||||
switch {
|
||||
case status >= 500 || (err != nil && err.Unexpected):
|
||||
case status >= 500 || (err != nil && err.StatusCode() >= 500):
|
||||
level = log.ErrorLevel
|
||||
case status >= 400:
|
||||
level = log.WarnLevel
|
||||
@@ -44,8 +44,10 @@ func LogResponse(reqID string, r *http.Request, status int, err *ierrors.Error,
|
||||
if err != nil {
|
||||
fields["error"] = err
|
||||
|
||||
if stack := err.FormatStack(); len(stack) > 0 {
|
||||
fields["stack"] = stack
|
||||
if level <= log.ErrorLevel {
|
||||
if stack := err.FormatStack(); len(stack) > 0 {
|
||||
fields["stack"] = stack
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -11,7 +10,6 @@ import (
|
||||
nanoid "github.com/matoous/go-nanoid/v2"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -159,7 +157,7 @@ func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
LogResponse(reqID, req, 404, ierrors.New(404, fmt.Sprintf("Route for %s is not defined", req.URL.Path), "Not found"))
|
||||
LogResponse(reqID, req, 404, newRouteNotDefinedError(req.URL.Path))
|
||||
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(404)
|
||||
|
@@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -34,11 +33,11 @@ func CheckTimeout(ctx context.Context) error {
|
||||
err := ctx.Err()
|
||||
switch err {
|
||||
case context.Canceled:
|
||||
return ierrors.New(499, fmt.Sprintf("Request was cancelled after %v", d), "Cancelled")
|
||||
return newRequestCancelledError(d)
|
||||
case context.DeadlineExceeded:
|
||||
return ierrors.New(http.StatusServiceUnavailable, fmt.Sprintf("Request was timed out after %v", d), "Timeout")
|
||||
return newRequestTimeoutError(d)
|
||||
default:
|
||||
return err
|
||||
return ierrors.Wrap(err, 0)
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
|
89
security/errors.go
Normal file
89
security/errors.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type (
|
||||
SignatureError string
|
||||
FileSizeError struct{}
|
||||
ImageResolutionError string
|
||||
SecurityOptionsError struct{}
|
||||
SourceURLError string
|
||||
SourceAddressError string
|
||||
)
|
||||
|
||||
func newSignatureError(msg string) error {
|
||||
return ierrors.Wrap(
|
||||
SignatureError(msg),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusForbidden),
|
||||
ierrors.WithPublicMessage("Forbidden"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e SignatureError) Error() string { return string(e) }
|
||||
|
||||
func newFileSizeError() error {
|
||||
return ierrors.Wrap(
|
||||
FileSizeError{},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
|
||||
ierrors.WithPublicMessage("Invalid source image"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e FileSizeError) Error() string { return "Source image file is too big" }
|
||||
|
||||
func newImageResolutionError(msg string) error {
|
||||
return ierrors.Wrap(
|
||||
ImageResolutionError(msg),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
|
||||
ierrors.WithPublicMessage("Invalid source image"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e ImageResolutionError) Error() string { return string(e) }
|
||||
|
||||
func newSecurityOptionsError() error {
|
||||
return ierrors.Wrap(
|
||||
SecurityOptionsError{},
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusForbidden),
|
||||
ierrors.WithPublicMessage("Invalid URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e SecurityOptionsError) Error() string { return "Security processing options are not allowed" }
|
||||
|
||||
func newSourceURLError(imageURL string) error {
|
||||
return ierrors.Wrap(
|
||||
SourceURLError(fmt.Sprintf("Source URL is not allowed: %s", imageURL)),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Invalid source URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e SourceURLError) Error() string { return string(e) }
|
||||
|
||||
func newSourceAddressError(msg string) error {
|
||||
return ierrors.Wrap(
|
||||
SourceAddressError(msg),
|
||||
1,
|
||||
ierrors.WithStatusCode(http.StatusNotFound),
|
||||
ierrors.WithPublicMessage("Invalid source URL"),
|
||||
ierrors.WithShouldReport(false),
|
||||
)
|
||||
}
|
||||
|
||||
func (e SourceAddressError) Error() string { return string(e) }
|
@@ -2,12 +2,8 @@ package security
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
var ErrSourceFileTooBig = ierrors.New(422, "Source image file is too big", "Invalid source image")
|
||||
|
||||
type hardLimitReader struct {
|
||||
r io.Reader
|
||||
left int
|
||||
@@ -15,7 +11,7 @@ type hardLimitReader struct {
|
||||
|
||||
func (lr *hardLimitReader) Read(p []byte) (n int, err error) {
|
||||
if lr.left <= 0 {
|
||||
return 0, ErrSourceFileTooBig
|
||||
return 0, newFileSizeError()
|
||||
}
|
||||
if len(p) > lr.left {
|
||||
p = p[0:lr.left]
|
||||
@@ -27,7 +23,7 @@ func (lr *hardLimitReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
func CheckFileSize(size int, opts Options) error {
|
||||
if opts.MaxSrcFileSize > 0 && size > opts.MaxSrcFileSize {
|
||||
return ErrSourceFileTooBig
|
||||
return newFileSizeError()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -1,23 +1,19 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
"github.com/imgproxy/imgproxy/v3/imath"
|
||||
)
|
||||
|
||||
var ErrSourceResolutionTooBig = ierrors.New(422, "Source image resolution is too big", "Invalid source image")
|
||||
var ErrSourceFrameResolutionTooBig = ierrors.New(422, "Source image frame resolution is too big", "Invalid source image")
|
||||
|
||||
func CheckDimensions(width, height, frames int, opts Options) error {
|
||||
frames = imath.Max(frames, 1)
|
||||
|
||||
if frames > 1 && opts.MaxAnimationFrameResolution > 0 {
|
||||
if width*height > opts.MaxAnimationFrameResolution {
|
||||
return ErrSourceFrameResolutionTooBig
|
||||
return newImageResolutionError("Source image frame resolution is too big")
|
||||
}
|
||||
} else {
|
||||
if width*height*frames > opts.MaxSrcResolution {
|
||||
return ErrSourceResolutionTooBig
|
||||
return newImageResolutionError("Source image resolution is too big")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,11 +2,8 @@ package security
|
||||
|
||||
import (
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
var ErrSecurityOptionsNotAllowed = ierrors.New(403, "Security processing options are not allowed", "Invalid URL")
|
||||
|
||||
type Options struct {
|
||||
MaxSrcResolution int
|
||||
MaxSrcFileSize int
|
||||
@@ -28,5 +25,5 @@ func IsSecurityOptionsAllowed() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ErrSecurityOptionsNotAllowed
|
||||
return newSecurityOptionsError()
|
||||
}
|
||||
|
@@ -4,16 +4,10 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidSignature = errors.New("Invalid signature")
|
||||
ErrInvalidSignatureEncoding = errors.New("Invalid signature encoding")
|
||||
)
|
||||
|
||||
func VerifySignature(signature, path string) error {
|
||||
if len(config.Keys) == 0 || len(config.Salts) == 0 {
|
||||
return nil
|
||||
@@ -27,7 +21,7 @@ func VerifySignature(signature, path string) error {
|
||||
|
||||
messageMAC, err := base64.RawURLEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return ErrInvalidSignatureEncoding
|
||||
return newSignatureError("Invalid signature encoding")
|
||||
}
|
||||
|
||||
for i := 0; i < len(config.Keys); i++ {
|
||||
@@ -36,7 +30,7 @@ func VerifySignature(signature, path string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return ErrInvalidSignature
|
||||
return newSignatureError("Invalid signature")
|
||||
}
|
||||
|
||||
func signatureFor(str string, key, salt []byte, signatureSize int) []byte {
|
||||
|
@@ -1,17 +1,12 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/config"
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
var ErrSourceAddressNotAllowed = errors.New("source address is not allowed")
|
||||
var ErrInvalidSourceAddress = errors.New("invalid source address")
|
||||
|
||||
func VerifySourceURL(imageURL string) error {
|
||||
if len(config.AllowedSources) == 0 {
|
||||
return nil
|
||||
@@ -23,11 +18,7 @@ func VerifySourceURL(imageURL string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return ierrors.New(
|
||||
404,
|
||||
fmt.Sprintf("Source URL is not allowed: %s", imageURL),
|
||||
"Invalid source",
|
||||
)
|
||||
return newSourceURLError(imageURL)
|
||||
}
|
||||
|
||||
func VerifySourceNetwork(addr string) error {
|
||||
@@ -38,19 +29,19 @@ func VerifySourceNetwork(addr string) error {
|
||||
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return ErrInvalidSourceAddress
|
||||
return newSourceAddressError(fmt.Sprintf("Invalid source address: %s", addr))
|
||||
}
|
||||
|
||||
if !config.AllowLoopbackSourceAddresses && (ip.IsLoopback() || ip.IsUnspecified()) {
|
||||
return ErrSourceAddressNotAllowed
|
||||
return newSourceAddressError(fmt.Sprintf("Loopback source address is not allowed: %s", addr))
|
||||
}
|
||||
|
||||
if !config.AllowLinkLocalSourceAddresses && (ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()) {
|
||||
return ErrSourceAddressNotAllowed
|
||||
return newSourceAddressError(fmt.Sprintf("Link-local source address is not allowed: %s", addr))
|
||||
}
|
||||
|
||||
if !config.AllowPrivateSourceAddresses && ip.IsPrivate() {
|
||||
return ErrSourceAddressNotAllowed
|
||||
return newSourceAddressError(fmt.Sprintf("Private source address is not allowed: %s", addr))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@@ -14,7 +14,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback bool
|
||||
allowLinkLocal bool
|
||||
allowPrivate bool
|
||||
expectedErr error
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "Invalid IP address",
|
||||
@@ -22,7 +22,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: ErrInvalidSourceAddress,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Loopback local not allowed",
|
||||
@@ -30,7 +30,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: false,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: ErrSourceAddressNotAllowed,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Loopback local allowed",
|
||||
@@ -38,7 +38,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: nil,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Unspecified (0.0.0.0) not allowed",
|
||||
@@ -46,7 +46,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: false,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: ErrSourceAddressNotAllowed,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Link local unicast not allowed",
|
||||
@@ -54,7 +54,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: false,
|
||||
allowPrivate: true,
|
||||
expectedErr: ErrSourceAddressNotAllowed,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Link local unicast allowed",
|
||||
@@ -62,7 +62,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: nil,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Private address not allowed",
|
||||
@@ -70,7 +70,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: false,
|
||||
expectedErr: ErrSourceAddressNotAllowed,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Private address allowed",
|
||||
@@ -78,7 +78,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: true,
|
||||
allowLinkLocal: true,
|
||||
allowPrivate: true,
|
||||
expectedErr: nil,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Global unicast should be allowed",
|
||||
@@ -86,7 +86,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: false,
|
||||
allowLinkLocal: false,
|
||||
allowPrivate: false,
|
||||
expectedErr: nil,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "Port in address with global IP",
|
||||
@@ -94,7 +94,7 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
allowLoopback: false,
|
||||
allowLinkLocal: false,
|
||||
allowPrivate: false,
|
||||
expectedErr: nil,
|
||||
expectErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -119,9 +119,8 @@ func TestVerifySourceNetwork(t *testing.T) {
|
||||
|
||||
err := VerifySourceNetwork(tc.addr)
|
||||
|
||||
if tc.expectedErr != nil {
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tc.expectedErr, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
20
server.go
20
server.go
@@ -20,11 +20,7 @@ import (
|
||||
"github.com/imgproxy/imgproxy/v3/vips"
|
||||
)
|
||||
|
||||
var (
|
||||
imgproxyIsRunningMsg = []byte("imgproxy is running")
|
||||
|
||||
errInvalidSecret = ierrors.New(403, "Invalid secret", "Forbidden")
|
||||
)
|
||||
var imgproxyIsRunningMsg = []byte("imgproxy is running")
|
||||
|
||||
func buildRouter() *router.Router {
|
||||
r := router.New(config.PathPrefix)
|
||||
@@ -125,7 +121,7 @@ func withSecret(h router.RouteHandler) router.RouteHandler {
|
||||
if subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), authHeader) == 1 {
|
||||
h(reqID, rw, r)
|
||||
} else {
|
||||
panic(errInvalidSecret)
|
||||
panic(newInvalidSecretError())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,21 +144,21 @@ func withPanicHandler(h router.RouteHandler) router.RouteHandler {
|
||||
panic(rerr)
|
||||
}
|
||||
|
||||
ierr := ierrors.Wrap(err, 2)
|
||||
ierr := ierrors.Wrap(err, 0)
|
||||
|
||||
if ierr.Unexpected {
|
||||
if ierr.ShouldReport() {
|
||||
errorreport.Report(err, r)
|
||||
}
|
||||
|
||||
router.LogResponse(reqID, r, ierr.StatusCode, ierr)
|
||||
router.LogResponse(reqID, r, ierr.StatusCode(), ierr)
|
||||
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.WriteHeader(ierr.StatusCode)
|
||||
rw.WriteHeader(ierr.StatusCode())
|
||||
|
||||
if config.DevelopmentErrorsMode {
|
||||
rw.Write([]byte(ierr.Message))
|
||||
rw.Write([]byte(ierr.Error()))
|
||||
} else {
|
||||
rw.Write([]byte(ierr.PublicMessage))
|
||||
rw.Write([]byte(ierr.PublicMessage()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
13
vips/errors.go
Normal file
13
vips/errors.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package vips
|
||||
|
||||
import (
|
||||
"github.com/imgproxy/imgproxy/v3/ierrors"
|
||||
)
|
||||
|
||||
type VipsError string
|
||||
|
||||
func newVipsError(msg string) error {
|
||||
return ierrors.Wrap(VipsError(msg), 2)
|
||||
}
|
||||
|
||||
func (e VipsError) Error() string { return string(e) }
|
11
vips/vips.go
11
vips/vips.go
@@ -11,6 +11,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -207,13 +208,15 @@ func Error() error {
|
||||
defer C.vips_error_clear()
|
||||
|
||||
errstr := strings.TrimSpace(C.GoString(C.vips_error_buffer()))
|
||||
err := ierrors.NewUnexpected(errstr, 1)
|
||||
err := newVipsError(errstr)
|
||||
|
||||
for _, re := range badImageErrRe {
|
||||
if re.MatchString(errstr) {
|
||||
err.StatusCode = 422
|
||||
err.PublicMessage = "Broken or unsupported image"
|
||||
break
|
||||
return ierrors.Wrap(
|
||||
err, 0,
|
||||
ierrors.WithStatusCode(http.StatusUnprocessableEntity),
|
||||
ierrors.WithPublicMessage("Broken or unsupported image"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user