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:
parent
8fd838a5cc
commit
13bafdbf9e
2 changed files with 54 additions and 4 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue