func TestStatus(t *testing.T) { status := Status{ Rules: []httpserver.HandlerConfig{ NewRule("/foo", http.StatusNotFound), NewRule("/teapot", http.StatusTeapot), NewRule("/foo/bar1", http.StatusInternalServerError), NewRule("/temporary-redirected", http.StatusTemporaryRedirect), }, Next: httpserver.HandlerFunc(urlPrinter), } tests := []struct { path string statusExpected bool status int }{ {"/foo", true, http.StatusNotFound}, {"/teapot", true, http.StatusTeapot}, {"/foo/bar", true, http.StatusNotFound}, {"/foo/bar1", true, http.StatusInternalServerError}, {"/someotherpath", false, 0}, {"/temporary-redirected", false, http.StatusTemporaryRedirect}, } for i, test := range tests { req, err := http.NewRequest("GET", test.path, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() actualStatus, err := status.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: Serving request failed with error %v", i, err) } if test.statusExpected { if test.status != actualStatus { t.Errorf("Test %d: Expected status code %d, got %d", i, test.status, actualStatus) } if rec.Body.String() != "" { t.Errorf("Test %d: Expected empty body, got '%s'", i, rec.Body.String()) } } else { if test.status != 0 { // Expecting status in response if test.status != rec.Code { t.Errorf("Test %d: Expected status code %d, got %d", i, test.status, rec.Code) } } else if rec.Body.String() != test.path { t.Errorf("Test %d: Expected body '%s', got '%s'", i, test.path, rec.Body.String()) } } } }
func TestMultipleHeaders(t *testing.T) { he := Headers{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { fmt.Fprint(w, "This is a test") return 0, nil }), Rules: []Rule{ {Path: "/a", Headers: []Header{ {Name: "+Link", Value: "</images/image.png>; rel=preload"}, {Name: "+Link", Value: "</css/main.css>; rel=preload"}, }}, }, } req, err := http.NewRequest("GET", "/a", nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } rec := httptest.NewRecorder() he.ServeHTTP(rec, req) desiredHeaders := []string{"</css/main.css>; rel=preload", "</images/image.png>; rel=preload"} actualHeaders := rec.HeaderMap[http.CanonicalHeaderKey("Link")] sort.Strings(actualHeaders) if !reflect.DeepEqual(desiredHeaders, actualHeaders) { t.Errorf("Expected header to contain: %v but got: %v", desiredHeaders, actualHeaders) } }
func TestExpVar(t *testing.T) { rw := ExpVar{ Next: httpserver.HandlerFunc(contentHandler), Resource: "/d/v", } tests := []struct { from string result int }{ {"/d/v", 0}, {"/x/y", http.StatusOK}, } for i, test := range tests { req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request %v", i, err) } rec := httptest.NewRecorder() result, err := rw.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: Could not ServeHTTP %v", i, err) } if result != test.result { t.Errorf("Test %d: Expected Header '%d' but was '%d'", i, test.result, result) } } }
func nextFunc(ss string) httpserver.Handler { return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { if _, err := w.Write([]byte(ss)); err != nil { return 500, err } return 200, nil }) }
func TestBrowseTemplate(t *testing.T) { tmpl, err := template.ParseFiles("testdata/photos.tpl") if err != nil { t.Fatalf("An error occured while parsing the template: %v", err) } b := Browse{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { t.Fatalf("Next shouldn't be called") return 0, nil }), Configs: []Config{ { PathScope: "/photos", Root: http.Dir("./testdata"), Template: tmpl, }, }, } req, err := http.NewRequest("GET", "/photos/", nil) if err != nil { t.Fatalf("Test: Could not create HTTP request: %v", err) } rec := httptest.NewRecorder() code, _ := b.ServeHTTP(rec, req) if code != http.StatusOK { t.Fatalf("Wrong status, expected %d, got %d", http.StatusOK, code) } respBody := rec.Body.String() expectedBody := `<!DOCTYPE html> <html> <head> <title>Template</title> </head> <body> <h1>Header</h1> <h1>/photos/</h1> <a href="./test.html">test.html</a><br> <a href="./test2.html">test2.html</a><br> <a href="./test3.html">test3.html</a><br> </body> </html> ` if respBody != expectedBody { t.Fatalf("Expected body: %v got: %v", expectedBody, respBody) } }
func TestLogRequestBody(t *testing.T) { var got bytes.Buffer logger := Logger{ Rules: []*Rule{{ PathScope: "/", Entries: []*Entry{{ Format: "{request_body}", Log: log.New(&got, "", 0), }}, }}, Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { // drain up body ioutil.ReadAll(r.Body) return 0, nil }), } for i, c := range []struct { body string expect string }{ {"", "\n"}, {"{hello} world!", "{hello} world!\n"}, {func() string { length := httpserver.MaxLogBodySize + 100 b := make([]byte, length) for i := 0; i < length; i++ { b[i] = 0xab } return string(b) }(), func() string { b := make([]byte, httpserver.MaxLogBodySize) for i := 0; i < httpserver.MaxLogBodySize; i++ { b[i] = 0xab } return string(b) + "\n" }(), }, } { got.Reset() r, err := http.NewRequest("POST", "/", bytes.NewBufferString(c.body)) if err != nil { t.Fatal(err) } r.Header.Set("Content-Type", "application/json") status, err := logger.ServeHTTP(httptest.NewRecorder(), r) if status != 0 { t.Errorf("case %d: Expected status to be 0, but was %d", i, status) } if err != nil { t.Errorf("case %d: Expected error to be nil, instead got: %v", i, err) } if got.String() != c.expect { t.Errorf("case %d: Expected body %q, but got %q", i, c.expect, got.String()) } } }
func genErrorHandler(status int, err error, body string) httpserver.Handler { return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { if len(body) > 0 { w.Header().Set("Content-Length", strconv.Itoa(len(body))) fmt.Fprint(w, body) } return status, err }) }
func newTestHandler(t *testing.T, caddyFile string) *handler { c := caddy.NewTestController("http", caddyFile) mc, err := parse(c) if err != nil { t.Fatal(err) } h := newHandler(mc, nil) h.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusTeapot, nil }) return h }
func TestHeader(t *testing.T) { hostname, err := os.Hostname() if err != nil { t.Fatalf("Could not determine hostname: %v", err) } for i, test := range []struct { from string name string value string }{ {"/a", "Foo", "Bar"}, {"/a", "Bar", ""}, {"/a", "Baz", ""}, {"/a", "Server", ""}, {"/a", "ServerName", hostname}, {"/b", "Foo", ""}, {"/b", "Bar", "Removed in /a"}, } { he := Headers{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { w.Header().Set("Bar", "Removed in /a") w.WriteHeader(http.StatusOK) return 0, nil }), Rules: []Rule{ {Path: "/a", Headers: http.Header{ "Foo": []string{"Bar"}, "ServerName": []string{"{hostname}"}, "-Bar": []string{""}, "-Server": []string{}, }}, }, } req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() // preset header rec.Header().Set("Server", "Caddy") he.ServeHTTP(rec, req) if got := rec.Header().Get(test.name); got != test.value { t.Errorf("Test %d: Expected %s header to be %q but was %q", i, test.name, test.value, got) } } }
func TestBasicAuth(t *testing.T) { rw := BasicAuth{ Next: httpserver.HandlerFunc(contentHandler), Rules: []Rule{ {Username: "******", Password: PlainMatcher("ttest"), Resources: []string{"/testing"}}, }, } tests := []struct { from string result int cred string }{ {"/testing", http.StatusUnauthorized, "ttest:test"}, {"/testing", http.StatusOK, "test:ttest"}, {"/testing", http.StatusUnauthorized, ""}, } for i, test := range tests { req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request %v", i, err) } auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(test.cred)) req.Header.Set("Authorization", auth) rec := httptest.NewRecorder() result, err := rw.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: Could not ServeHTTP %v", i, err) } if result != test.result { t.Errorf("Test %d: Expected Header '%d' but was '%d'", i, test.result, result) } if result == http.StatusUnauthorized { headers := rec.Header() if val, ok := headers["Www-Authenticate"]; ok { if val[0] != "Basic" { t.Errorf("Test %d, Www-Authenticate should be %s provided %s", i, "Basic", val[0]) } } else { t.Errorf("Test %d, should provide a header Www-Authenticate", i) } } } }
func nextFunc(shouldMime bool, contentType string) httpserver.Handler { return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { if shouldMime { if w.Header().Get("Content-Type") != contentType { return 0, fmt.Errorf("expected Content-Type: %v, found %v", contentType, r.Header.Get("Content-Type")) } return 0, nil } if w.Header().Get("Content-Type") != "" { return 0, fmt.Errorf("Content-Type header not expected") } return 0, nil }) }
func TestResponseFilterWriter(t *testing.T) { tests := []struct { body string shouldCompress bool }{ {"Hello\t\t\t\n", false}, {"Hello the \t\t\t world is\n\n\n great", true}, {"Hello \t\t\nfrom gzip", true}, {"Hello gzip\n", false}, } filters := []ResponseFilter{ LengthFilter(15), } server := Gzip{Configs: []Config{ {ResponseFilters: filters}, }} for i, ts := range tests { server.Next = httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { w.Header().Set("Content-Length", fmt.Sprint(len(ts.body))) w.Write([]byte(ts.body)) return 200, nil }) r := urlRequest("/") r.Header.Set("Accept-Encoding", "gzip") w := httptest.NewRecorder() server.ServeHTTP(w, r) resp := w.Body.String() if !ts.shouldCompress { if resp != ts.body { t.Errorf("Test %v: No compression expected, found %v", i, resp) } } else { if resp == ts.body { t.Errorf("Test %v: Compression expected, found %v", i, resp) } } } }
func TestMultiEntries(t *testing.T) { var ( got1 bytes.Buffer got2 bytes.Buffer ) logger := Logger{ Rules: []*Rule{{ PathScope: "/", Entries: []*Entry{ { Format: "foo {request_body}", Log: log.New(&got1, "", 0), }, { Format: "{method} {request_body}", Log: log.New(&got2, "", 0), }, }, }}, Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { // drain up body ioutil.ReadAll(r.Body) return 0, nil }), } r, err := http.NewRequest("POST", "/", bytes.NewBufferString("hello world")) if err != nil { t.Fatal(err) } r.Header.Set("Content-Type", "application/json") status, err := logger.ServeHTTP(httptest.NewRecorder(), r) if status != 0 { t.Errorf("Expected status to be 0, but was %d", status) } if err != nil { t.Errorf("Expected error to be nil, instead got: %v", err) } if got, expect := got1.String(), "foo hello world\n"; got != expect { t.Errorf("Expected %q, but got %q", expect, got) } if got, expect := got2.String(), "POST hello world\n"; got != expect { t.Errorf("Expected %q, but got %q", expect, got) } }
func TestMultipleOverlappingRules(t *testing.T) { rw := BasicAuth{ Next: httpserver.HandlerFunc(contentHandler), Rules: []Rule{ {Username: "******", Password: PlainMatcher("p1"), Resources: []string{"/t"}}, {Username: "******", Password: PlainMatcher("p2"), Resources: []string{"/t/t"}}, }, } tests := []struct { from string result int cred string }{ {"/t", http.StatusOK, "t:p1"}, {"/t/t", http.StatusOK, "t:p1"}, {"/t/t", http.StatusOK, "t1:p2"}, {"/a", http.StatusOK, "t1:p2"}, {"/t/t", http.StatusUnauthorized, "t1:p3"}, {"/t", http.StatusUnauthorized, "t1:p2"}, } for i, test := range tests { req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request %v", i, err) } auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(test.cred)) req.Header.Set("Authorization", auth) rec := httptest.NewRecorder() result, err := rw.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: Could not ServeHTTP %v", i, err) } if result != test.result { t.Errorf("Test %d: Expected Header '%d' but was '%d'", i, test.result, result) } } }
func nextFunc(shouldGzip bool) httpserver.Handler { return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { // write a relatively large text file b, err := ioutil.ReadFile("testdata/test.txt") if err != nil { return 500, err } if _, err := w.Write(b); err != nil { return 500, err } if shouldGzip { if r.Header.Get("Accept-Encoding") != "" { return 0, fmt.Errorf("Accept-Encoding header not expected") } if w.Header().Get("Content-Encoding") != "gzip" { return 0, fmt.Errorf("Content-Encoding must be gzip, found %v", r.Header.Get("Content-Encoding")) } if w.Header().Get("Vary") != "Accept-Encoding" { return 0, fmt.Errorf("Vary must be Accept-Encoding, found %v", r.Header.Get("Vary")) } if _, ok := w.(*gzipResponseWriter); !ok { return 0, fmt.Errorf("ResponseWriter should be gzipResponseWriter, found %T", w) } if strings.Contains(w.Header().Get("Content-Type"), "application/x-gzip") { return 0, fmt.Errorf("Content type should not be gzip.") } return 0, nil } if r.Header.Get("Accept-Encoding") == "" { return 0, fmt.Errorf("Accept-Encoding header expected") } if w.Header().Get("Content-Encoding") == "gzip" { return 0, fmt.Errorf("Content-Encoding must not be gzip, found gzip") } if _, ok := w.(*gzipResponseWriter); ok { return 0, fmt.Errorf("ResponseWriter should not be gzipResponseWriter") } return 0, nil }) }
func TestInternal(t *testing.T) { im := Internal{ Next: httpserver.HandlerFunc(internalTestHandlerFunc), Paths: []string{"/internal"}, } tests := []struct { url string expectedCode int expectedBody string }{ {"/internal", http.StatusNotFound, ""}, {"/public", 0, "/public"}, {"/public/internal", 0, "/public/internal"}, {"/redirect", 0, "/internal"}, {"/cycle", http.StatusInternalServerError, ""}, } for i, test := range tests { req, err := http.NewRequest("GET", test.url, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() code, _ := im.ServeHTTP(rec, req) if code != test.expectedCode { t.Errorf("Test %d: Expected status code %d for %s, but got %d", i, test.expectedCode, test.url, code) } if rec.Body.String() != test.expectedBody { t.Errorf("Test %d: Expected body '%s' for %s, but got '%s'", i, test.expectedBody, test.url, rec.Body.String()) } } }
func setup(c *caddy.Controller) error { rules, err := parseRules(c) if err != nil { return err } siteConfig := httpserver.GetConfig(c) siteConfig.AddMiddleware(func(next httpserver.Handler) httpserver.Handler { return httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { for _, rule := range rules { if httpserver.Path(r.URL.Path).Matches(rule.Path) { rule.Conf.HandleRequest(w, r) if cors.IsPreflight(r) { return 200, nil } break } } return next.ServeHTTP(w, r) }) }) return nil }
func TestServeHTTP(t *testing.T) { h := Handler{ Next: httpserver.HandlerFunc(nextHandler), Mux: NewMux(), } w := httptest.NewRecorder() r, err := http.NewRequest("GET", "/debug/pprof", nil) if err != nil { t.Fatal(err) } status, err := h.ServeHTTP(w, r) if status != 0 { t.Errorf("Expected status %d but got %d", 0, status) } if err != nil { t.Errorf("Expected nil error, but got: %v", err) } if w.Body.String() == "content" { t.Errorf("Expected pprof to handle request, but it didn't") } w = httptest.NewRecorder() r, err = http.NewRequest("GET", "/foo", nil) if err != nil { t.Fatal(err) } status, err = h.ServeHTTP(w, r) if status != http.StatusNotFound { t.Errorf("Test two: Expected status %d but got %d", http.StatusNotFound, status) } if err != nil { t.Errorf("Test two: Expected nil error, but got: %v", err) } if w.Body.String() != "content" { t.Errorf("Expected pprof to pass the request thru, but it didn't; got: %s", w.Body.String()) } }
func TestBrowseHTTPMethods(t *testing.T) { tmpl, err := template.ParseFiles("testdata/photos.tpl") if err != nil { t.Fatalf("An error occured while parsing the template: %v", err) } b := Browse{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusTeapot, nil // not t.Fatalf, or we will not see what other methods yield }), Configs: []Config{ { PathScope: "/photos", Root: http.Dir("./testdata"), Template: tmpl, }, }, } rec := httptest.NewRecorder() for method, expected := range map[string]int{ http.MethodGet: http.StatusOK, http.MethodHead: http.StatusOK, http.MethodOptions: http.StatusNotImplemented, "PROPFIND": http.StatusNotImplemented, } { req, err := http.NewRequest(method, "/photos/", nil) if err != nil { t.Fatalf("Test: Could not create HTTP request: %v", err) } code, _ := b.ServeHTTP(rec, req) if code != expected { t.Errorf("Wrong status with HTTP Method %s: expected %d, got %d", method, expected, code) } } }
func TestVisibleErrorWithPanic(t *testing.T) { const panicMsg = "I'm a panic" eh := ErrorHandler{ ErrorPages: make(map[int]string), Debug: true, Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { panic(panicMsg) }), } req, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatal(err) } rec := httptest.NewRecorder() code, err := eh.ServeHTTP(rec, req) if code != 0 { t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code) } if err != nil { t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err) } body := rec.Body.String() if !strings.Contains(body, "[PANIC /] caddyhttp/errors/errors_test.go") { t.Errorf("Expected response body to contain error log line, but it didn't:\n%s", body) } if !strings.Contains(body, panicMsg) { t.Errorf("Expected response body to contain panic message, but it didn't:\n%s", body) } if len(body) < 500 { t.Errorf("Expected response body to contain stack trace, but it was too short: len=%d", len(body)) } }
func TestIPv6(t *testing.T) { TestCases := []struct { inputIpfilterConfig string shouldErr bool reqIP string reqPath string expectedStatus int }{ { `ipfilter / { rule allow ip 2001:db8:1234::/48 }`, false, "[2001:db8:1234:0000:0000:0000:0000:0000]:_", "/", http.StatusOK, }, { `ipfilter / { rule allow ip 2001:db8:1234::/48 }`, false, "[2001:db8:1234:ffff:ffff:ffff:ffff:ffff]:_", "/", http.StatusOK, }, { `ipfilter / { rule allow ip 2001:db8:1234::/48 }`, false, "[2001:db8:1244:0000:0000:0000:0000:0000]:_", "/", http.StatusForbidden, }, { `ipfilter / { rule allow ip 8.8.8.8 2001:db8:85a3:8d3:1319:8a2e:370:7348 8.8.4.4 }`, false, "[2001:db8:85a3:8d3:1319:8a2e:370:7338]:_", "/", http.StatusForbidden, }, { `ipfilter / { rule allow ip 8.8.8.8 2001:db8:85a3:8d3:1319:8a2e:370:7348 8.8.4.4 }`, false, "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:_", "/", http.StatusOK, }, { `ipfilter / { rule allow ip 2001:db8:85a3::8a2e:370:7334 10.0.0 192.168.1.5-40 }`, false, "192.168.1.33:_", "/", http.StatusOK, }, { `ipfilter / { rule allow ip 2001:db8:85a3::8a2e:370:7334/64 10.0.0 }`, false, "10.0.0.5:_", "/", http.StatusOK, }, } for i, tc := range TestCases { // Parse the text config c := caddy.NewTestController("http", tc.inputIpfilterConfig) config, err := ipfilterParse(c) if err != nil && !tc.shouldErr { t.Errorf("Test %d failed, error generated while it should not: %v", i, err) } else if err == nil && tc.shouldErr { t.Errorf("Test %d failed, no error generated while it should", i) } else if err != nil { continue } ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: config, } req, err := http.NewRequest("GET", tc.reqPath, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP rec := httptest.NewRecorder() status, err := ipf.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d failed. Error generated:\n%v", i, err) } if status != tc.expectedStatus { t.Fatalf("Test %d failed. Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", i, tc.expectedStatus, status, tc) } } }
func TestStrict(t *testing.T) { TestCases := []struct { ipfconf IPFConfig reqIP string fwdFor string scope string expectedStatus int }{ { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), Strict: true, }, }, }, "8.8.4.4:_", "8.8.8.8", "/", http.StatusOK, }, { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), Strict: true, }, }, }, "8.8.8.8:_", "8.8.8.8", "/", http.StatusForbidden, }, { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), Strict: false, }, }, }, "8.8.4.4:_", "8.8.8.8", "/", http.StatusForbidden, }, } for _, tc := range TestCases { ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: tc.ipfconf, } req, err := http.NewRequest("GET", tc.scope, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP if tc.fwdFor != "" { req.Header.Set("X-Forwarded-For", tc.fwdFor) } rec := httptest.NewRecorder() status, _ := ipf.ServeHTTP(rec, req) if status != tc.expectedStatus { t.Fatalf("Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", tc.expectedStatus, status, tc) } } }
func TestMultipleIpFilters(t *testing.T) { TestCases := []struct { inputIpfilterConfig string shouldErr bool reqIP string reqPath string expectedStatus int }{ { `ipfilter / { rule block ip 192.168.1.10 } ipfilter /allowed { rule allow ip 192.168.1.10 }`, false, "192.168.1.10:_", "/", http.StatusForbidden, }, { `ipfilter / { rule block ip 192.168.1.10 } ipfilter /allowed { rule allow ip 192.168.1.10 }`, false, "192.168.1.10:_", "/allowed", http.StatusOK, }, { `ipfilter / { rule block ip 192.168.1.10 } ipfilter /allowed { rule allow ip 192.168.1.10 }`, false, "212.168.23.13:_", "/", http.StatusOK, }, { `ipfilter / { rule block ip 192.168.1.10 } ipfilter /allowed { rule allow ip 192.168.1.10 }`, false, "212.168.23.13:_", "/allowed", http.StatusForbidden, }, { fmt.Sprintf(`ipfilter / { rule allow ip 192.168.1.10 } ipfilter /allowed { rule allow country US database %s }`, DataBase), false, "8.8.8.8:_", "/allowed", http.StatusOK, }, { fmt.Sprintf(`ipfilter /local { rule allow ip 192.168.1 } ipfilter /private { rule allow ip 192.168.1.10-15 } ipfilter /notglobal /secret { rule block country RU database %s } ipfilter / { rule allow ip 212.222.222.1 }`, DataBase), false, "192.168.1.9:_", "/private", http.StatusForbidden, }, { fmt.Sprintf(`ipfilter /local { rule allow ip 192.168.1 } ipfilter /private { rule allow ip 192.168.1.10-15 } ipfilter /notglobal /secret { rule block country RU database %s } ipfilter / { rule allow ip 212.222.222.1 }`, DataBase), false, "212.222.222.1:_", "/list", http.StatusOK, }, { fmt.Sprintf(`ipfilter /local { rule allow ip 192.168.1 } ipfilter /private { rule allow ip 192.168.1.10-15 } ipfilter /notglobal /secret { rule block country RU database %s } ipfilter / { rule allow ip 212.222.222.1 }`, DataBase), false, "5.175.96.22:_", "/secret", http.StatusForbidden, }, { fmt.Sprintf(`ipfilter /local { rule allow ip 192.168.1 } ipfilter /private { rule allow ip 192.168.1.10-15 } ipfilter /notglobal /secret { rule block country RU database %s } ipfilter / { rule allow ip 212.222.222.1 }`, DataBase), false, "192.168.1.14:_", "/local", http.StatusOK, }, { fmt.Sprintf(`ipfilter /local { rule allow ip 192.168.1 } ipfilter /private { rule allow ip 192.168.1.10-15 } ipfilter /notglobal /secret { rule block country RU database %s } ipfilter / { rule allow ip 212.222.222.1 }`, DataBase), false, "192.168.1.16:_", "/private", http.StatusForbidden, }, } for i, tc := range TestCases { // Parse the text config c := caddy.NewTestController("http", tc.inputIpfilterConfig) config, err := ipfilterParse(c) if err != nil && !tc.shouldErr { t.Errorf("Test %d failed, error generated while it should not: %v", i, err) } else if err == nil && tc.shouldErr { t.Errorf("Test %d failed, no error generated while it should", i) } else if err != nil { continue } ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: config, } req, err := http.NewRequest("GET", tc.reqPath, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP rec := httptest.NewRecorder() status, err := ipf.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d failed. Error generated:\n%v", i, err) } if status != tc.expectedStatus { t.Fatalf("Test %d failed. Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", i, tc.expectedStatus, status, tc) } } }
func TestCountryCodes(t *testing.T) { TestCases := []struct { ipfconf IPFConfig reqIP string scope string expectedBody string expectedStatus int }{ {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, BlockPage: BlockPage, IsBlock: false, CountryCodes: []string{"JP", "SA"}, }, }, }, "8.8.8.8:_", // US "/", BlockMsg, http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/private"}, BlockPage: BlockPage, IsBlock: true, CountryCodes: []string{"US", "CA"}, }, }, }, "24.53.192.20:_", // CA "/private", BlockMsg, http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/testdata"}, IsBlock: true, CountryCodes: []string{"RU", "CN"}, }, }, }, "42.48.120.7:_", // CN "/", "", http.StatusOK, // pass-thru, out of scope }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, CountryCodes: []string{"RU", "JP", "SA"}, }, }, }, "78.95.221.163:_", // SA "/", "", http.StatusForbidden, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/onlyus"}, IsBlock: false, CountryCodes: []string{"US"}, }, }, }, "5.175.96.22:_", // RU "/onlyus", "", http.StatusForbidden, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: false, CountryCodes: []string{"FR", "GB", "AE", "DE"}, }, }, }, "5.4.9.3:_", // DE "/", "", http.StatusOK, // Allowed }, } // open the db db, err := maxminddb.Open(DataBase) if err != nil { t.Fatalf("Error opening the database: %v", err) } defer db.Close() for _, tc := range TestCases { ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: tc.ipfconf, } // set the DBHandler ipf.Config.DBHandler = db req, err := http.NewRequest("GET", tc.scope, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP rec := httptest.NewRecorder() status, _ := ipf.ServeHTTP(rec, req) if status != tc.expectedStatus { t.Fatalf("Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", tc.expectedStatus, status, tc) } if rec.Body.String() != tc.expectedBody { t.Fatalf("Expected Body: '%s', Got: '%s'\nTestCase: %v\n", tc.expectedBody, rec.Body.String(), tc) } } }
func TestFwdForIPs(t *testing.T) { // These test cases provide test coverage for proxied requests support (Refer to https://github.com/pyed/ipfilter/pull/4) TestCases := []struct { ipfconf IPFConfig reqIP string fwdFor string scope string expectedStatus int }{ // Middleware should block request when filtering rule is set to 'Block', a *blocked* IP is passed in the 'X-Forwarded-For' header and the request is coming from *permitted* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "8.8.8.8", "/", http.StatusForbidden, }, // Middleware should allow request when filtering rule is set to 'Block', no IP is passed in the 'X-Forwarded-For' header and the request is coming from *permitted* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "", "/", http.StatusOK, }, // Middleware should allow request when filtering rule is set to 'Block', a *permitted* IP is passed in the 'X-Forwarded-For' header and the request is coming from *blocked* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.8.8:_", "8.8.4.4", "/", http.StatusOK, }, // Middleware should allow request when filtering rule is set to 'Allow', a *permitted* IP is passed in the 'X-Forwarded-For' header and the request is coming from *blocked* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: false, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "8.8.8.8", "/", http.StatusOK, }, // Middleware should block request when filtering rule is set to 'Allow', no IP is passed in the 'X-Forwarded-For' header and the request is coming from *blocked* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: false, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "", "/", http.StatusForbidden, }, // Middleware should block request when filtering rule is set to 'Allow', a *blocked* IP is passed in the 'X-Forwarded-For' header and the request is coming from *permitted* remote address { IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: false, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.8.8:_", "8.8.4.4", "/", http.StatusForbidden, }, } for _, tc := range TestCases { ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: tc.ipfconf, } req, err := http.NewRequest("GET", tc.scope, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP if tc.fwdFor != "" { req.Header.Set("X-Forwarded-For", tc.fwdFor) } rec := httptest.NewRecorder() status, _ := ipf.ServeHTTP(rec, req) if status != tc.expectedStatus { t.Fatalf("Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", tc.expectedStatus, status, tc) } } }
func TestNets(t *testing.T) { TestCases := []struct { ipfconf IPFConfig reqIP string scope string expectedBody string expectedStatus int }{ {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, BlockPage: BlockPage, IsBlock: true, Nets: parseCIDRs([]string{"243.1.3.10/31", "243.1.3.12/30", "243.1.3.16/30", "243.1.3.20/32"}), }, }, }, "243.1.3.15:_", "/", BlockMsg, http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/private"}, BlockPage: BlockPage, IsBlock: true, Nets: parseCIDRs([]string{"243.1.3.0/24", "202.33.44.0/24"}), }, }, }, "202.33.44.224:_", "/private", BlockMsg, http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, BlockPage: BlockPage, IsBlock: true, Nets: parseCIDRs([]string{ "243.1.3.10/31", "243.1.3.12/30", "243.1.3.16/30", "243.1.3.20/32", }), }, }, }, "243.1.3.9:_", "/", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/eighties"}, BlockPage: BlockPage, IsBlock: false, Nets: parseCIDRs([]string{ "243.1.3.10/31", "243.1.3.12/30", "243.1.3.16/30", "243.1.3.20/32", "80.0.0.0/8", }), }, }, }, "80.245.155.250:_", "/eighties", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/eighties"}, IsBlock: true, Nets: parseCIDRs([]string{ "243.1.3.10/31", "243.1.3.12/30", "243.1.3.16/30", "243.1.3.20/32", "80.0.0.0/8", }), }, }, }, "80.245.155.250:_", "/", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, IsBlock: true, Nets: parseCIDRs([]string{ "243.1.3.10/31", "243.1.3.12/30", "243.1.3.16/30", "243.1.3.20/32", "80.0.0.0/8", "23.1.3.1/32", "23.1.3.2/31", "23.1.3.4/30", "23.1.3.8/29", "23.1.3.16/30", "23.1.3.20/32", "85.0.0.0/8", }), }, }, }, "23.1.3.9:_", "/", "", http.StatusForbidden, }, // From here on out, tests are covering single IPNets {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, BlockPage: BlockPage, IsBlock: true, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "/", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/"}, BlockPage: BlockPage, IsBlock: false, Nets: parseCIDRs([]string{"8.8.8.8/32"}), }, }, }, "8.8.4.4:_", "/", BlockMsg, http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/private"}, BlockPage: BlockPage, IsBlock: false, Nets: parseCIDRs([]string{ "52.9.1.2/32", "52.9.1.3/32", "52.9.1.4/32", }), }, }, }, "52.9.1.3:_", "/private", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/private"}, BlockPage: BlockPage, IsBlock: false, Nets: parseCIDRs([]string{"99.1.8.8/32"}), }, }, }, "90.90.90.90:_", "/", "", http.StatusOK, }, {IPFConfig{ Paths: []IPPath{ { PathScopes: []string{"/private"}, IsBlock: true, Nets: parseCIDRs([]string{ "52.9.1.2/32", "52.9.1.3/32", "52.9.1.4/32", }), }, }, }, "52.9.1.3:_", "/private", "", http.StatusForbidden, }, } for _, tc := range TestCases { ipf := IPFilter{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return http.StatusOK, nil }), Config: tc.ipfconf, } req, err := http.NewRequest("GET", tc.scope, nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } req.RemoteAddr = tc.reqIP rec := httptest.NewRecorder() status, _ := ipf.ServeHTTP(rec, req) if status != tc.expectedStatus { t.Fatalf("Expected StatusCode: '%d', Got: '%d'\nTestCase: %v\n", tc.expectedStatus, status, tc) } if rec.Body.String() != tc.expectedBody { t.Fatalf("Expected Body: '%s', Got: '%s'\nTestCase: %v\n", tc.expectedBody, rec.Body.String(), tc) } } }
func TestBrowseJson(t *testing.T) { b := Browse{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { t.Fatalf("Next shouldn't be called") return 0, nil }), Configs: []Config{ { PathScope: "/photos/", Root: http.Dir("./testdata"), }, }, } //Getting the listing from the ./testdata/photos, the listing returned will be used to validate test results testDataPath := filepath.Join("./testdata", "photos") file, err := os.Open(testDataPath) if err != nil { if os.IsPermission(err) { t.Fatalf("Os Permission Error") } } defer file.Close() files, err := file.Readdir(-1) if err != nil { t.Fatalf("Unable to Read Contents of the directory") } var fileinfos []FileInfo for i, f := range files { name := f.Name() // Tests fail in CI environment because all file mod times are the same for // some reason, making the sorting unpredictable. To hack around this, // we ensure here that each file has a different mod time. chTime := f.ModTime().UTC().Add(-(time.Duration(i) * time.Second)) if err := os.Chtimes(filepath.Join(testDataPath, name), chTime, chTime); err != nil { t.Fatal(err) } if f.IsDir() { name += "/" } url := url.URL{Path: "./" + name} fileinfos = append(fileinfos, FileInfo{ IsDir: f.IsDir(), Name: f.Name(), Size: f.Size(), URL: url.String(), ModTime: chTime, Mode: f.Mode(), }) } listing := Listing{Items: fileinfos} // this listing will be used for validation inside the tests tests := []struct { QueryURL string SortBy string OrderBy string Limit int shouldErr bool expectedResult []FileInfo }{ //test case 1: testing for default sort and order and without the limit parameter, default sort is by name and the default order is ascending //without the limit query entire listing will be produced {"/", "", "", -1, false, listing.Items}, //test case 2: limit is set to 1, orderBy and sortBy is default {"/?limit=1", "", "", 1, false, listing.Items[:1]}, //test case 3 : if the listing request is bigger than total size of listing then it should return everything {"/?limit=100000000", "", "", 100000000, false, listing.Items}, //test case 4 : testing for negative limit {"/?limit=-1", "", "", -1, false, listing.Items}, //test case 5 : testing with limit set to -1 and order set to descending {"/?limit=-1&order=desc", "", "desc", -1, false, listing.Items}, //test case 6 : testing with limit set to 2 and order set to descending {"/?limit=2&order=desc", "", "desc", 2, false, listing.Items}, //test case 7 : testing with limit set to 3 and order set to descending {"/?limit=3&order=desc", "", "desc", 3, false, listing.Items}, //test case 8 : testing with limit set to 3 and order set to ascending {"/?limit=3&order=asc", "", "asc", 3, false, listing.Items}, //test case 9 : testing with limit set to 1111111 and order set to ascending {"/?limit=1111111&order=asc", "", "asc", 1111111, false, listing.Items}, //test case 10 : testing with limit set to default and order set to ascending and sorting by size {"/?order=asc&sort=size", "size", "asc", -1, false, listing.Items}, //test case 11 : testing with limit set to default and order set to ascending and sorting by last modified {"/?order=asc&sort=time", "time", "asc", -1, false, listing.Items}, //test case 12 : testing with limit set to 1 and order set to ascending and sorting by last modified {"/?order=asc&sort=time&limit=1", "time", "asc", 1, false, listing.Items}, //test case 13 : testing with limit set to -100 and order set to ascending and sorting by last modified {"/?order=asc&sort=time&limit=-100", "time", "asc", -100, false, listing.Items}, //test case 14 : testing with limit set to -100 and order set to ascending and sorting by size {"/?order=asc&sort=size&limit=-100", "size", "asc", -100, false, listing.Items}, } for i, test := range tests { var marsh []byte req, err := http.NewRequest("GET", "/photos"+test.QueryURL, nil) if err == nil && test.shouldErr { t.Errorf("Test %d didn't error, but it should have", i) } else if err != nil && !test.shouldErr { t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err) } req.Header.Set("Accept", "application/json") rec := httptest.NewRecorder() code, err := b.ServeHTTP(rec, req) if code != http.StatusOK { t.Fatalf("In test %d: Wrong status, expected %d, got %d", i, http.StatusOK, code) } if rec.HeaderMap.Get("Content-Type") != "application/json; charset=utf-8" { t.Fatalf("Expected Content type to be application/json; charset=utf-8, but got %s ", rec.HeaderMap.Get("Content-Type")) } actualJSONResponse := rec.Body.String() copyOflisting := listing if test.SortBy == "" { copyOflisting.Sort = "name" } else { copyOflisting.Sort = test.SortBy } if test.OrderBy == "" { copyOflisting.Order = "asc" } else { copyOflisting.Order = test.OrderBy } copyOflisting.applySort() limit := test.Limit if limit <= len(copyOflisting.Items) && limit > 0 { marsh, err = json.Marshal(copyOflisting.Items[:limit]) } else { // if the 'limit' query is empty, or has the wrong value, list everything marsh, err = json.Marshal(copyOflisting.Items) } if err != nil { t.Fatalf("Unable to Marshal the listing ") } expectedJSON := string(marsh) if actualJSONResponse != expectedJSON { t.Errorf("JSON response doesn't match the expected for test number %d with sort=%s, order=%s\nExpected response %s\nActual response = %s\n", i+1, test.SortBy, test.OrderBy, expectedJSON, actualJSONResponse) } } }
func TestRewrite(t *testing.T) { rw := Rewrite{ Next: httpserver.HandlerFunc(urlPrinter), Rules: []httpserver.HandlerConfig{ NewSimpleRule("/from", "/to"), NewSimpleRule("/a", "/b"), NewSimpleRule("/b", "/b{uri}"), }, FileSys: http.Dir("."), } regexps := [][]string{ {"/reg/", ".*", "/to", ""}, {"/r/", "[a-z]+", "/toaz", "!.html|"}, {"/url/", "a([a-z0-9]*)s([A-Z]{2})", "/to/{path}", ""}, {"/ab/", "ab", "/ab?{query}", ".txt|"}, {"/ab/", "ab", "/ab?type=html&{query}", ".html|"}, {"/abc/", "ab", "/abc/{file}", ".html|"}, {"/abcd/", "ab", "/a/{dir}/{file}", ".html|"}, {"/abcde/", "ab", "/a#{fragment}", ".html|"}, {"/ab/", `.*\.jpg`, "/ajpg", ""}, {"/reggrp", `/ad/([0-9]+)([a-z]*)`, "/a{1}/{2}", ""}, {"/reg2grp", `(.*)`, "/{1}", ""}, {"/reg3grp", `(.*)/(.*)/(.*)`, "/{1}{2}{3}", ""}, {"/hashtest", "(.*)", "/{1}", ""}, } for _, regexpRule := range regexps { var ext []string if s := strings.Split(regexpRule[3], "|"); len(s) > 1 { ext = s[:len(s)-1] } rule, err := NewComplexRule(regexpRule[0], regexpRule[1], regexpRule[2], 0, ext, httpserver.IfMatcher{}) if err != nil { t.Fatal(err) } rw.Rules = append(rw.Rules, rule) } tests := []struct { from string expectedTo string }{ {"/from", "/to"}, {"/a", "/b"}, {"/b", "/b/b"}, {"/aa", "/aa"}, {"/", "/"}, {"/a?foo=bar", "/b?foo=bar"}, {"/asdf?foo=bar", "/asdf?foo=bar"}, {"/foo#bar", "/foo#bar"}, {"/a#foo", "/b#foo"}, {"/reg/foo", "/to"}, {"/re", "/re"}, {"/r/", "/r/"}, {"/r/123", "/r/123"}, {"/r/a123", "/toaz"}, {"/r/abcz", "/toaz"}, {"/r/z", "/toaz"}, {"/r/z.html", "/r/z.html"}, {"/r/z.js", "/toaz"}, {"/url/asAB", "/to/url/asAB"}, {"/url/aBsAB", "/url/aBsAB"}, {"/url/a00sAB", "/to/url/a00sAB"}, {"/url/a0z0sAB", "/to/url/a0z0sAB"}, {"/ab/aa", "/ab/aa"}, {"/ab/ab", "/ab/ab"}, {"/ab/ab.txt", "/ab"}, {"/ab/ab.txt?name=name", "/ab?name=name"}, {"/ab/ab.html?name=name", "/ab?type=html&name=name"}, {"/abc/ab.html", "/abc/ab.html"}, {"/abcd/abcd.html", "/a/abcd/abcd.html"}, {"/abcde/abcde.html", "/a"}, {"/abcde/abcde.html#1234", "/a#1234"}, {"/ab/ab.jpg", "/ajpg"}, {"/reggrp/ad/12", "/a12/"}, {"/reggrp/ad/124a", "/a124/a"}, {"/reggrp/ad/124abc", "/a124/abc"}, {"/reg2grp/ad/124abc", "/ad/124abc"}, {"/reg3grp/ad/aa/66", "/adaa66"}, {"/reg3grp/ad612/n1n/ab", "/ad612n1nab"}, {"/hashtest/a%20%23%20test", "/a%20%23%20test"}, {"/hashtest/a%20%3F%20test", "/a%20%3F%20test"}, {"/hashtest/a%20%3F%23test", "/a%20%3F%23test"}, } for i, test := range tests { req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() rw.ServeHTTP(rec, req) if rec.Body.String() != test.expectedTo { t.Errorf("Test %d: Expected URL to be '%s' but was '%s'", i, test.expectedTo, rec.Body.String()) } } statusTests := []struct { status int base string to string regexp string statusExpected bool }{ {400, "/status", "", "", true}, {400, "/ignore", "", "", false}, {400, "/", "", "^/ignore", false}, {400, "/", "", "(.*)", true}, {400, "/status", "", "", true}, } for i, s := range statusTests { urlPath := fmt.Sprintf("/status%d", i) rule, err := NewComplexRule(s.base, s.regexp, s.to, s.status, nil, httpserver.IfMatcher{}) if err != nil { t.Fatalf("Test %d: No error expected for rule but found %v", i, err) } rw.Rules = []httpserver.HandlerConfig{rule} req, err := http.NewRequest("GET", urlPath, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } rec := httptest.NewRecorder() code, err := rw.ServeHTTP(rec, req) if err != nil { t.Fatalf("Test %d: No error expected for handler but found %v", i, err) } if s.statusExpected { if rec.Body.String() != "" { t.Errorf("Test %d: Expected empty body but found %s", i, rec.Body.String()) } if code != s.status { t.Errorf("Test %d: Expected status code %d found %d", i, s.status, code) } } else { if code != 0 { t.Errorf("Test %d: Expected no status code found %d", i, code) } } } }
func TestRedirect(t *testing.T) { for i, test := range []struct { from string expectedLocation string expectedCode int }{ {"http://localhost/from", "/to", http.StatusMovedPermanently}, {"http://localhost/a", "/b", http.StatusTemporaryRedirect}, {"http://localhost/aa", "", http.StatusOK}, {"http://localhost/", "", http.StatusOK}, {"http://localhost/a?foo=bar", "/b", http.StatusTemporaryRedirect}, {"http://localhost/asdf?foo=bar", "", http.StatusOK}, {"http://localhost/foo#bar", "", http.StatusOK}, {"http://localhost/a#foo", "/b", http.StatusTemporaryRedirect}, // The scheme checks that were added to this package don't actually // help with redirects because of Caddy's design: a redirect middleware // for http will always be different than the redirect middleware for // https because they have to be on different listeners. These tests // just go to show extra bulletproofing, I guess. {"http://localhost/scheme", "https://localhost/scheme", http.StatusMovedPermanently}, {"https://localhost/scheme", "", http.StatusOK}, {"https://localhost/scheme2", "http://localhost/scheme2", http.StatusMovedPermanently}, {"http://localhost/scheme2", "", http.StatusOK}, {"http://localhost/scheme3", "https://localhost/scheme3", http.StatusMovedPermanently}, {"https://localhost/scheme3", "", http.StatusOK}, } { var nextCalled bool re := Redirect{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { nextCalled = true return 0, nil }), Rules: []Rule{ {FromPath: "/from", To: "/to", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, {FromPath: "/a", To: "/b", Code: http.StatusTemporaryRedirect, RequestMatcher: httpserver.IfMatcher{}}, // These http and https schemes would never actually be mixed in the same // redirect rule with Caddy because http and https schemes have different listeners, // so they don't share a redirect rule. So although these tests prove something // impossible with Caddy, it's extra bulletproofing at very little cost. {FromScheme: "http", FromPath: "/scheme", To: "https://localhost/scheme", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, {FromScheme: "https", FromPath: "/scheme2", To: "http://localhost/scheme2", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, {FromScheme: "", FromPath: "/scheme3", To: "https://localhost/scheme3", Code: http.StatusMovedPermanently, RequestMatcher: httpserver.IfMatcher{}}, }, } req, err := http.NewRequest("GET", test.from, nil) if err != nil { t.Fatalf("Test %d: Could not create HTTP request: %v", i, err) } if strings.HasPrefix(test.from, "https://") { req.TLS = new(tls.ConnectionState) // faux HTTPS } rec := httptest.NewRecorder() re.ServeHTTP(rec, req) if rec.Header().Get("Location") != test.expectedLocation { t.Errorf("Test %d: Expected Location header to be %q but was %q", i, test.expectedLocation, rec.Header().Get("Location")) } if rec.Code != test.expectedCode { t.Errorf("Test %d: Expected status code to be %d but was %d", i, test.expectedCode, rec.Code) } if nextCalled && test.expectedLocation != "" { t.Errorf("Test %d: Next handler was unexpectedly called", i) } } }
func TestTemplates(t *testing.T) { tmpl := Templates{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return 0, nil }), Rules: []Rule{ { Extensions: []string{".html"}, IndexFiles: []string{"index.html"}, Path: "/photos", }, { Extensions: []string{".html", ".htm"}, IndexFiles: []string{"index.html", "index.htm"}, Path: "/images", Delims: [2]string{"{%", "%}"}, }, }, Root: "./testdata", FileSys: http.Dir("./testdata"), } tmplroot := Templates{ Next: httpserver.HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return 0, nil }), Rules: []Rule{ { Extensions: []string{".html"}, IndexFiles: []string{"index.html"}, Path: "/", }, }, Root: "./testdata", FileSys: http.Dir("./testdata"), } // Test tmpl on /photos/test.html req, err := http.NewRequest("GET", "/photos/test.html", nil) if err != nil { t.Fatalf("Test: Could not create HTTP request: %v", err) } rec := httptest.NewRecorder() tmpl.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) } respBody := rec.Body.String() expectedBody := `<!DOCTYPE html><html><head><title>test page</title></head><body><h1>Header title</h1> </body></html> ` if respBody != expectedBody { t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) } // Test tmpl on /images/img.htm req, err = http.NewRequest("GET", "/images/img.htm", nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } rec = httptest.NewRecorder() tmpl.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) } respBody = rec.Body.String() expectedBody = `<!DOCTYPE html><html><head><title>img</title></head><body><h1>Header title</h1> </body></html> ` if respBody != expectedBody { t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) } // Test tmpl on /images/img2.htm req, err = http.NewRequest("GET", "/images/img2.htm", nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } rec = httptest.NewRecorder() tmpl.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) } respBody = rec.Body.String() expectedBody = `<!DOCTYPE html><html><head><title>img</title></head><body>{{.Include "header.html"}}</body></html> ` if respBody != expectedBody { t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) } // Test tmplroot on /root.html req, err = http.NewRequest("GET", "/root.html", nil) if err != nil { t.Fatalf("Could not create HTTP request: %v", err) } rec = httptest.NewRecorder() tmplroot.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("Test: Wrong response code: %d, should be %d", rec.Code, http.StatusOK) } respBody = rec.Body.String() expectedBody = `<!DOCTYPE html><html><head><title>root</title></head><body><h1>Header title</h1> </body></html> ` if respBody != expectedBody { t.Fatalf("Test: the expected body %v is different from the response one: %v", expectedBody, respBody) } }