From 0b5fd397fd908d005b39d2d55e2e12581c937368 Mon Sep 17 00:00:00 2001
From: Will McCutchen <will@mccutch.org>
Date: Fri, 14 Oct 2016 16:51:28 -0700
Subject: [PATCH] Add /redirect/:n handler

---
 httpbin/handlers.go      | 10 +++++++
 httpbin/handlers_test.go | 62 ++++++++++++++++++++++++++++++++++++++++
 httpbin/httpbin.go       |  1 +
 3 files changed, 73 insertions(+)

diff --git a/httpbin/handlers.go b/httpbin/handlers.go
index a325101..eefc635 100644
--- a/httpbin/handlers.go
+++ b/httpbin/handlers.go
@@ -197,6 +197,7 @@ func redirectLocation(r *http.Request, relative bool, n int) string {
 	} else {
 		u := getURL(r)
 		u.Path = path
+		u.RawQuery = ""
 		location = u.String()
 	}
 
@@ -218,6 +219,15 @@ func doRedirect(w http.ResponseWriter, r *http.Request, relative bool) {
 	w.WriteHeader(http.StatusFound)
 }
 
+// Redirect responds with 302 redirect a given number of times. Defaults to a
+// relative redirect, but an ?absolute=true query param will trigger an
+// absolute redirect.
+func (h *HTTPBin) Redirect(w http.ResponseWriter, r *http.Request) {
+	params := r.URL.Query()
+	relative := strings.ToLower(params.Get("absolute")) != "true"
+	doRedirect(w, r, relative)
+}
+
 // RelativeRedirect responds with an HTTP 302 redirect a given number of times
 func (h *HTTPBin) RelativeRedirect(w http.ResponseWriter, r *http.Request) {
 	doRedirect(w, r, true)
diff --git a/httpbin/handlers_test.go b/httpbin/handlers_test.go
index 5dd6e94..601b764 100644
--- a/httpbin/handlers_test.go
+++ b/httpbin/handlers_test.go
@@ -728,6 +728,68 @@ func TestResponseHeaders__OverrideContentType(t *testing.T) {
 	assertContentType(t, w, contentType)
 }
 
+func TestRedirect__OK(t *testing.T) {
+	var tests = []struct {
+		relative bool
+		n        int
+		location string
+	}{
+		{true, 1, "/get"},
+		{true, 2, "/relative-redirect/1"},
+		{true, 100, "/relative-redirect/99"},
+
+		{false, 1, "http://host/get"},
+		{false, 2, "http://host/absolute-redirect/1"},
+		{false, 100, "http://host/absolute-redirect/99"},
+	}
+
+	for _, test := range tests {
+		u := fmt.Sprintf("/redirect/%d", test.n)
+		if !test.relative {
+			u = fmt.Sprintf("%s?absolute=true", u)
+		}
+		r, _ := http.NewRequest("GET", u, nil)
+		r.Host = "host"
+		w := httptest.NewRecorder()
+		handler.ServeHTTP(w, r)
+
+		assertStatusCode(t, w, http.StatusFound)
+		assertHeader(t, w, "Location", test.location)
+	}
+}
+
+func TestRedirect__Errors(t *testing.T) {
+	var tests = []struct {
+		relative bool
+		given    interface{}
+		status   int
+	}{
+		{true, 3.14, http.StatusBadRequest},
+		{true, -1, http.StatusBadRequest},
+		{true, "", http.StatusBadRequest},
+		{true, "foo", http.StatusBadRequest},
+		{true, "10/bar", http.StatusNotFound},
+
+		{false, 3.14, http.StatusBadRequest},
+		{false, -1, http.StatusBadRequest},
+		{false, "", http.StatusBadRequest},
+		{false, "foo", http.StatusBadRequest},
+		{false, "10/bar", http.StatusNotFound},
+	}
+
+	for _, test := range tests {
+		u := fmt.Sprintf("/redirect/%v", test.given)
+		if !test.relative {
+			u = fmt.Sprintf("%s?absolute=true", u)
+		}
+		r, _ := http.NewRequest("GET", u, nil)
+		w := httptest.NewRecorder()
+		handler.ServeHTTP(w, r)
+
+		assertStatusCode(t, w, test.status)
+	}
+}
+
 func TestAbsoluteAndRelativeRedirects__OK(t *testing.T) {
 	var tests = []struct {
 		relative bool
diff --git a/httpbin/httpbin.go b/httpbin/httpbin.go
index 2dffdb4..35fca2f 100644
--- a/httpbin/httpbin.go
+++ b/httpbin/httpbin.go
@@ -67,6 +67,7 @@ func (h *HTTPBin) Handler() http.Handler {
 	mux.HandleFunc("/status/", h.Status)
 	mux.HandleFunc("/response-headers", h.ResponseHeaders)
 
+	mux.HandleFunc("/redirect/", h.Redirect)
 	mux.HandleFunc("/relative-redirect/", h.RelativeRedirect)
 	mux.HandleFunc("/absolute-redirect/", h.AbsoluteRedirect)
 
-- 
GitLab