package webui import ( "net/url" "testing" ) // TestParseOriginPutsTheOriginInTheBrowserForm pins what an accepted Origin // compares against: a flag's https://Host:443 and the Origin header a browser // sends for the same page, https://host, must be the same origin, or every POST // from the published page answers 403. It needs no request or daemon: the // normalization happens before either sees the origin. func TestParseOriginPutsTheOriginInTheBrowserForm(t *testing.T) { for _, test := range []struct { name string text string want string wantErr bool }{ {name: "tailnet name", text: "https://albedo.example.ts.net", want: "https://albedo.example.ts.net"}, {name: "trailing slash", text: "https://albedo.example.ts.net/", want: "https://albedo.example.ts.net"}, {name: "surrounding spaces", text: " http://albedo.example.ts.net:8080 ", want: "http://albedo.example.ts.net:8080"}, {name: "mixed case host", text: "https://Albedo.Example.TS.net", want: "https://albedo.example.ts.net"}, {name: "https default port", text: "https://albedo.example.ts.net:443", want: "https://albedo.example.ts.net"}, {name: "http default port", text: "http://albedo.example.ts.net:80", want: "http://albedo.example.ts.net"}, {name: "ipv6 default port", text: "https://[::1]:443", want: "https://[::1]"}, {name: "ipv6 other port", text: "https://[::1]:8443", want: "https://[::1]:8443"}, {name: "empty", text: "", wantErr: true}, {name: "no scheme", text: "albedo.example.ts.net", wantErr: true}, {name: "no host", text: "https://", wantErr: true}, {name: "path", text: "https://albedo.example.ts.net/app", wantErr: true}, {name: "query", text: "https://albedo.example.ts.net/?key=abc", wantErr: true}, {name: "fragment", text: "https://albedo.example.ts.net/#top", wantErr: true}, {name: "other scheme", text: "ftp://albedo.example.ts.net", wantErr: true}, {name: "credentials", text: "https://user@albedo.example.ts.net", wantErr: true}, } { t.Run(test.name, func(t *testing.T) { parsed, err := ParseOrigin(test.text) if test.wantErr { if err == nil { t.Errorf("ParseOrigin(%q) = %q, want an error", test.text, parsed) } return } if err != nil { t.Fatalf("ParseOrigin(%q): %v", test.text, err) } if got := parsed.String(); got != test.want { t.Errorf("ParseOrigin(%q) = %q, want %q", test.text, got, test.want) } }) } } // TestSameOriginIgnoresHowTheOriginWasWritten pins the comparison used by the // Origin check: the port is a number, not text, and the scheme still counts. func TestSameOriginIgnoresHowTheOriginWasWritten(t *testing.T) { flag, err := ParseOrigin("https://Albedo.Example.TS.net:443") if err != nil { t.Fatal(err) } header, err := ParseOrigin("https://albedo.example.ts.net") if err != nil { t.Fatal(err) } if !SameOrigin(flag, header) { t.Errorf("%q and %q are the same origin", flag, header) } // A proxy may forward the default port the browser left out of the // header, so the port is compared as a number, not as text. forwarded := &url.URL{Scheme: "https", Host: "albedo.example.ts.net:443"} if !SameOrigin(header, forwarded) { t.Errorf("%q and %q are the same origin", header, forwarded) } otherScheme, err := ParseOrigin("http://albedo.example.ts.net:443") if err != nil { t.Fatal(err) } if SameOrigin(header, otherScheme) { t.Errorf("%q and %q differ in scheme", header, otherScheme) } if SameOrigin(header, nil) || !SameOrigin(nil, nil) { t.Error("nil is no origin") } }