diff --git a/imageproxy.go b/imageproxy.go index 20d854c..a5686cd 100644 --- a/imageproxy.go +++ b/imageproxy.go @@ -31,6 +31,9 @@ import ( tphttp "willnorris.com/go/imageproxy/third_party/http" ) +// Maximum number of redirection-followings allowed. +const maxRedirects = 10 + // Proxy serves image requests. type Proxy struct { 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 { // FollowRedirects is true (default), ensure that the redirected host is allowed 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)) { http.Error(w, msgNotAllowedInRedirect, http.StatusForbidden) return errNotAllowed @@ -285,9 +294,10 @@ func copyHeader(dst, src http.Header, headerNames ...string) { } var ( - errReferrer = errors.New("request does not contain an allowed referrer") - errDeniedHost = errors.New("request contains a denied host") - errNotAllowed = errors.New("request does not contain an allowed host or valid signature") + errReferrer = errors.New("request does not contain an allowed referrer") + errDeniedHost = errors.New("request contains a denied host") + 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" msgNotAllowedInRedirect = "requested URL in redirect is not allowed" diff --git a/imageproxy_test.go b/imageproxy_test.go index 87c2f5d..86bd6a6 100644 --- a/imageproxy_test.go +++ b/imageproxy_test.go @@ -17,6 +17,8 @@ import ( "net/url" "os" "reflect" + "regexp" + "strconv" "strings" "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()) 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)) @@ -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) { var b strings.Builder