From 1462a07cf4d0ffa034c9d438484faa2f553ed995 Mon Sep 17 00:00:00 2001
From: Will McCutchen <will@mccutch.org>
Date: Tue, 30 May 2017 17:29:50 -0700
Subject: [PATCH] Add /redirect-to

---
 httpbin/handlers.go      | 26 +++++++++++++++++++++++
 httpbin/handlers_test.go | 45 ++++++++++++++++++++++++++++++++++++++++
 httpbin/httpbin.go       |  2 ++
 3 files changed, 73 insertions(+)

diff --git a/httpbin/handlers.go b/httpbin/handlers.go
index fb27c4b..29ed07d 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 d08e382..5dd8424 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 8e835cb..69d38cd 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)
-- 
GitLab