0
Fork 0
mirror of https://github.com/willnorris/imageproxy.git synced 2024-12-16 21:56:43 -05:00

Add MaxRedirects option

Add `MaxRedirects` option to set maximum redirection-followings allowed.
The option is only valid when `FollowRedirects` is `true`.

Being able to limit the amount of redirections is helpful in order to
avoid possible loops of redirections or just too long round trips.
This commit is contained in:
Jacopo 2021-11-15 15:29:18 +01:00 committed by Will Norris
parent 8fd838a5cc
commit 13bafdbf9e
2 changed files with 54 additions and 4 deletions

View file

@ -31,6 +31,9 @@ import (
tphttp "willnorris.com/go/imageproxy/third_party/http" tphttp "willnorris.com/go/imageproxy/third_party/http"
) )
// Maximum number of redirection-followings allowed.
const maxRedirects = 10
// Proxy serves image requests. // Proxy serves image requests.
type Proxy struct { type Proxy struct {
Client *http.Client // client used to fetch remote URLs Client *http.Client // client used to fetch remote URLs
@ -189,6 +192,12 @@ func (p *Proxy) serveImage(w http.ResponseWriter, r *http.Request) {
if p.FollowRedirects { if p.FollowRedirects {
// FollowRedirects is true (default), ensure that the redirected host is allowed // FollowRedirects is true (default), ensure that the redirected host is allowed
p.Client.CheckRedirect = func(newreq *http.Request, via []*http.Request) error { p.Client.CheckRedirect = func(newreq *http.Request, via []*http.Request) error {
if len(via) > maxRedirects {
if p.Verbose {
p.logf("followed too many redirects (%d).", len(via))
}
return errTooManyRedirects
}
if hostMatches(p.DenyHosts, newreq.URL) || (len(p.AllowHosts) > 0 && !hostMatches(p.AllowHosts, newreq.URL)) { if hostMatches(p.DenyHosts, newreq.URL) || (len(p.AllowHosts) > 0 && !hostMatches(p.AllowHosts, newreq.URL)) {
http.Error(w, msgNotAllowedInRedirect, http.StatusForbidden) http.Error(w, msgNotAllowedInRedirect, http.StatusForbidden)
return errNotAllowed return errNotAllowed
@ -285,9 +294,10 @@ func copyHeader(dst, src http.Header, headerNames ...string) {
} }
var ( var (
errReferrer = errors.New("request does not contain an allowed referrer") errReferrer = errors.New("request does not contain an allowed referrer")
errDeniedHost = errors.New("request contains a denied host") errDeniedHost = errors.New("request contains a denied host")
errNotAllowed = errors.New("request does not contain an allowed host or valid signature") errNotAllowed = errors.New("request does not contain an allowed host or valid signature")
errTooManyRedirects = errors.New("too many redirects")
msgNotAllowed = "requested URL is not allowed" msgNotAllowed = "requested URL is not allowed"
msgNotAllowedInRedirect = "requested URL in redirect is not allowed" msgNotAllowedInRedirect = "requested URL in redirect is not allowed"

View file

@ -17,6 +17,8 @@ import (
"net/url" "net/url"
"os" "os"
"reflect" "reflect"
"regexp"
"strconv"
"strings" "strings"
"testing" "testing"
) )
@ -349,7 +351,17 @@ func (t testTransport) RoundTrip(req *http.Request) (*http.Response, error) {
raw = fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\nContent-Type: image/png\n\n%s", len(img.Bytes()), img.Bytes()) raw = fmt.Sprintf("HTTP/1.1 200 OK\nContent-Length: %d\nContent-Type: image/png\n\n%s", len(img.Bytes()), img.Bytes())
default: default:
raw = "HTTP/1.1 404 Not Found\n\n" redirectRegexp := regexp.MustCompile(`/redirects-(\d+)`)
if redirectRegexp.MatchString(req.URL.Path) {
redirectsLeft, _ := strconv.ParseUint(redirectRegexp.FindStringSubmatch(req.URL.Path)[1], 10, 8)
if redirectsLeft == 0 {
raw = "HTTP/1.1 200 OK\n\n"
} else {
raw = fmt.Sprintf("HTTP/1.1 302\nLocation: /http://redirect.test/redirects-%d\n\n", redirectsLeft-1)
}
} else {
raw = "HTTP/1.1 404 Not Found\n\n"
}
} }
buf := bufio.NewReader(bytes.NewBufferString(raw)) buf := bufio.NewReader(bytes.NewBufferString(raw))
@ -414,6 +426,34 @@ func TestProxy_ServeHTTP_is304(t *testing.T) {
} }
} }
func TestProxy_ServeHTTP_maxRedirects(t *testing.T) {
p := &Proxy{
Client: &http.Client{
Transport: testTransport{},
},
FollowRedirects: true,
}
tests := []struct {
url string
code int
}{
{"/http://redirect.test/redirects-0", http.StatusOK},
{"/http://redirect.test/redirects-2", http.StatusOK},
{"/http://redirect.test/redirects-11", http.StatusInternalServerError}, // too many redirects
}
for _, tt := range tests {
req, _ := http.NewRequest("GET", "http://localhost"+tt.url, nil)
resp := httptest.NewRecorder()
p.ServeHTTP(resp, req)
if got, want := resp.Code, tt.code; got != want {
t.Errorf("ServeHTTP(%v) returned status %d, want %d", req, got, want)
}
}
}
func TestProxy_log(t *testing.T) { func TestProxy_log(t *testing.T) {
var b strings.Builder var b strings.Builder