Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | package network |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "net/http" |
| 6 | "net/http/httptest" |
| 7 | "testing" |
| 8 | |
| 9 | "go.uber.org/zap" |
| 10 | |
| 11 | "github.com/caddyserver/caddy/v2" |
| 12 | ) |
| 13 | |
| 14 | // TestProxyFromURLPlaceholderHostValidation checks that a network_proxy URL |
| 15 | // which resolves to a value without a host is rejected after placeholders are |
| 16 | // applied. url.Parse accepts both "http://:80" and "/some/path" without |
| 17 | // returning an error, so ProxyFunc has to reject them itself. |
| 18 | func TestProxyFromURLPlaceholderHostValidation(t *testing.T) { |
| 19 | for i, tc := range []struct { |
| 20 | name string |
| 21 | url string |
| 22 | hostRepl string |
| 23 | wantHost string |
| 24 | wantErr bool |
| 25 | }{ |
| 26 | { |
| 27 | name: "port only, no host", |
| 28 | url: "http://{proxy.host}:80", |
| 29 | hostRepl: "", |
| 30 | wantErr: true, |
| 31 | }, |
| 32 | { |
| 33 | name: "no scheme or host, path only", |
| 34 | url: "{proxy.host}/some/path", |
| 35 | hostRepl: "", |
| 36 | wantErr: true, |
| 37 | }, |
| 38 | { |
| 39 | // url.Parse puts the userinfo elsewhere, so Host is still just |
| 40 | // ":8080" here. The comment above the check doesn't name this |
| 41 | // form, but it is equally host-less and equally unusable. |
| 42 | name: "userinfo but no host", |
| 43 | url: "http://user:pass@{proxy.host}:8080", |
| 44 | hostRepl: "", |
| 45 | wantErr: true, |
| 46 | }, |
| 47 | { |
| 48 | name: "host and port", |
| 49 | url: "http://{proxy.host}:8080", |
| 50 | hostRepl: "proxy.example.com", |
| 51 | wantHost: "proxy.example.com", |
| 52 | wantErr: false, |
| 53 | }, |
| 54 | { |
| 55 | name: "host without port", |
| 56 | url: "http://{proxy.host}", |
| 57 | hostRepl: "proxy.example.com", |
| 58 | wantHost: "proxy.example.com", |
| 59 | wantErr: false, |
| 60 | }, |
| 61 | { |
| 62 | // Guards against a repair that splits Host on ":" instead of |
| 63 | // using Hostname(): an IPv6 literal is full of colons and must |
| 64 | // not be mistaken for a missing host. |
| 65 | name: "IPv6 literal with port", |
| 66 | url: "http://[{proxy.host}]:8080", |
| 67 | hostRepl: "::1", |
| 68 | wantHost: "::1", |
| 69 | wantErr: false, |
| 70 | }, |
| 71 | { |
| 72 | name: "IPv6 literal without port", |
| 73 | url: "http://[{proxy.host}]", |
| 74 | hostRepl: "2001:db8::1", |
| 75 | wantHost: "2001:db8::1", |
| 76 | wantErr: false, |
| 77 | }, |
| 78 | } { |
| 79 | p := ProxyFromURL{URL: tc.url, logger: zap.NewNop()} |
| 80 | |
| 81 | repl := caddy.NewReplacer() |
| 82 | repl.Set("proxy.host", tc.hostRepl) |
| 83 | |
| 84 | req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil) |
| 85 | req = req.WithContext(context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)) |
| 86 | |
| 87 | proxyURL, err := p.ProxyFunc()(req) |
| 88 | |
| 89 | if tc.wantErr { |
| 90 | if err == nil { |
| 91 | t.Errorf("Test %d (%s): expected an error for %q but got none (proxy URL: %v)", |
| 92 | i, tc.name, tc.url, proxyURL) |
| 93 | } |
| 94 | if proxyURL != nil { |
| 95 | t.Errorf("Test %d (%s): expected a nil proxy URL for %q, got %v", |
| 96 | i, tc.name, tc.url, proxyURL) |
| 97 | } |
| 98 | continue |
| 99 | } |
| 100 | |
| 101 | if err != nil { |
| 102 | t.Errorf("Test %d (%s): unexpected error for %q: %v", i, tc.name, tc.url, err) |
| 103 | continue |
| 104 | } |
| 105 | if proxyURL == nil { |
| 106 | t.Errorf("Test %d (%s): expected a proxy URL for %q, got nil", i, tc.name, tc.url) |
| 107 | continue |
| 108 | } |
| 109 | if proxyURL.Hostname() != tc.wantHost { |
| 110 | t.Errorf("Test %d (%s): expected host %q, got %q", |
| 111 | i, tc.name, tc.wantHost, proxyURL.Hostname()) |
| 112 | } |
| 113 | } |
| 114 | } |