| 1 | package docs |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | ) |
| 7 | |
| 8 | // TestValidateOriginRejectsWhitespace is fix round 1's own regression |
| 9 | // test: DOCS_ORIGIN=" " (all whitespace) is non-empty, so a bare |
| 10 | // cfg.Origin == "" check in New lets it through, and csrf.Protect then |
| 11 | // compares every request's real Origin/Referer against literal |
| 12 | // whitespace — never matching, so the app boots looking healthy and |
| 13 | // 403s all traffic instead of refusing to start. validateOrigin must |
| 14 | // catch it (New trims before calling this, but the function itself |
| 15 | // stays correct against an untrimmed caller too). |
| 16 | func TestValidateOriginRejectsWhitespace(t *testing.T) { |
| 17 | for _, origin := range []string{"", " ", "\t\n "} { |
| 18 | if err := validateOrigin(origin); err == nil { |
| 19 | t.Errorf("validateOrigin(%q) = nil, want an error", origin) |
| 20 | } |
| 21 | } |
| 22 | } |
| 23 | |
| 24 | // TestValidateOriginRejectsMalformed covers the other ways a |
| 25 | // non-blank string can still be unusable as csrf.Protect's |
| 26 | // comparison origin: no scheme, the wrong scheme, no host, and a URL |
| 27 | // carrying more than scheme://host (a path, a query, a fragment, |
| 28 | // embedded userinfo) — none of which a real browser's Origin header |
| 29 | // would ever equal. |
| 30 | func TestValidateOriginRejectsMalformed(t *testing.T) { |
| 31 | cases := []string{ |
| 32 | "not a url at all", |
| 33 | "localhost:8080", // no recognized scheme |
| 34 | "ftp://example.com", // wrong scheme |
| 35 | "http://", // no host |
| 36 | "http://:8099", // Host is ":8099", Hostname() is "" — see TestValidateOriginRejectsHostlessAddr |
| 37 | "http://example.com/path", // path |
| 38 | "http://example.com?x=1", // query |
| 39 | "http://example.com#frag", // fragment |
| 40 | "http://user:pass@example.com", // userinfo |
| 41 | } |
| 42 | for _, origin := range cases { |
| 43 | t.Run(origin, func(t *testing.T) { |
| 44 | if err := validateOrigin(origin); err == nil { |
| 45 | t.Errorf("validateOrigin(%q) = nil, want an error", origin) |
| 46 | } |
| 47 | }) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | // TestValidateOriginRejectsHostlessAddr is fix round 2's own |
| 52 | // regression test: -addr :8099 — the plan's own example invocation — |
| 53 | // makes main.go derive "http://" + ":8099" = "http://:8099". Its |
| 54 | // url.Host is ":8099" (non-empty), so a `u.Host == ""` check let it |
| 55 | // through; its url.Hostname() is "" (Host minus the port, and there |
| 56 | // is nothing before the colon), which is the actual answer to "is |
| 57 | // there a host here at all". A reviewer booted a production-Env |
| 58 | // binary on exactly this origin and watched it serve before this fix. |
| 59 | func TestValidateOriginRejectsHostlessAddr(t *testing.T) { |
| 60 | const origin = "http://:8099" |
| 61 | err := validateOrigin(origin) |
| 62 | if err == nil { |
| 63 | t.Fatalf("validateOrigin(%q) = nil, want an error — Host is non-empty but Hostname() is empty", origin) |
| 64 | } |
| 65 | if !strings.Contains(err.Error(), "no host") { |
| 66 | t.Errorf("error should say there is no host: %v", err) |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | // TestValidateOriginAcceptsGoodOrigins proves the check above is not |
| 71 | // so strict it rejects the values this app actually uses: |
| 72 | // DefaultOrigin, an -addr-derived origin, and a real platform origin |
| 73 | // with a port. |
| 74 | func TestValidateOriginAcceptsGoodOrigins(t *testing.T) { |
| 75 | for _, origin := range []string{ |
| 76 | DefaultOrigin, |
| 77 | "http://localhost:8099", |
| 78 | "https://docs.bds.oncarlos.com", |
| 79 | } { |
| 80 | if err := validateOrigin(origin); err != nil { |
| 81 | t.Errorf("validateOrigin(%q) = %v, want nil", origin, err) |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | |