diff --git a/httpbin/handlers.go b/httpbin/handlers.go index fb27c4b489cae252572d3825040e2ffb1ec6b172..29ed07d4067783d19e1c39c99dfbcb897157fbf1 100644 --- a/httpbin/handlers.go +++ b/httpbin/handlers.go @@ -293,6 +293,32 @@ func (h *HTTPBin) AbsoluteRedirect(w http.ResponseWriter, r *http.Request) { doRedirect(w, r, false) } +// RedirectTo responds with a redirect to a specific URL with an optional +// status code, which defaults to 302 +func (h *HTTPBin) RedirectTo(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query() + + url := q.Get("url") + if url == "" { + http.Error(w, "Missing URL", http.StatusBadRequest) + return + } + + var err error + statusCode := http.StatusFound + rawStatusCode := q.Get("status_code") + if rawStatusCode != "" { + statusCode, err = strconv.Atoi(q.Get("status_code")) + if err != nil || statusCode < 300 || statusCode > 399 { + http.Error(w, "Invalid status code", http.StatusBadRequest) + return + } + } + + w.Header().Set("Location", url) + w.WriteHeader(statusCode) +} + // Cookies responds with the cookies in the incoming request func (h *HTTPBin) Cookies(w http.ResponseWriter, r *http.Request) { resp := cookiesResponse{} diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go index d08e38298a64408240ee23d29d6684b862c3d63b..5dd842488e6f08e252de222344b708fcd1e57837 100644 --- a/httpbin/handlers_test.go +++ b/httpbin/handlers_test.go @@ -823,6 +823,51 @@ func TestRedirects(t *testing.T) { } } +func TestRedirectTo(t *testing.T) { + var okTests = []struct { + url string + expectedLocation string + expectedStatus int + }{ + {"/redirect-to?url=http://www.example.com/", "http://www.example.com/", http.StatusFound}, + {"/redirect-to?url=http://www.example.com/&status_code=307", "http://www.example.com/", http.StatusTemporaryRedirect}, + + {"/redirect-to?url=/get", "/get", http.StatusFound}, + {"/redirect-to?url=/get&status_code=307", "/get", http.StatusTemporaryRedirect}, + + {"/redirect-to?url=foo", "foo", http.StatusFound}, + } + + for _, test := range okTests { + t.Run("ok"+test.url, func(t *testing.T) { + r, _ := http.NewRequest("GET", test.url, nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + assertStatusCode(t, w, test.expectedStatus) + assertHeader(t, w, "Location", test.expectedLocation) + }) + } + + var badTests = []struct { + url string + expectedStatus int + }{ + {"/redirect-to", http.StatusBadRequest}, + {"/redirect-to?status_code=302", http.StatusBadRequest}, + {"/redirect-to?url=foo&status_code=418", http.StatusBadRequest}, + } + for _, test := range badTests { + t.Run("bad"+test.url, func(t *testing.T) { + r, _ := http.NewRequest("GET", test.url, nil) + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + + assertStatusCode(t, w, test.expectedStatus) + }) + } +} + func TestCookies(t *testing.T) { testCookies := func(t *testing.T, cookies cookiesResponse) { r, _ := http.NewRequest("GET", "/cookies", nil) diff --git a/httpbin/httpbin.go b/httpbin/httpbin.go index 8e835cb748403eaf7c0353d9f8936305791129b6..69d38cdb484508697ac9d8683ace7ece816f66e0 100644 --- a/httpbin/httpbin.go +++ b/httpbin/httpbin.go @@ -102,9 +102,11 @@ func (h *HTTPBin) Handler() http.Handler { mux.HandleFunc("/response-headers", h.ResponseHeaders) mux.HandleFunc("/status/", h.Status) + mux.HandleFunc("/redirect/", h.Redirect) mux.HandleFunc("/relative-redirect/", h.RelativeRedirect) mux.HandleFunc("/absolute-redirect/", h.AbsoluteRedirect) + mux.HandleFunc("/redirect-to", h.RedirectTo) mux.HandleFunc("/cookies", h.Cookies) mux.HandleFunc("/cookies/set", h.SetCookies)