| 1 | package callscore |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "errors" |
| 6 | "io" |
| 7 | "net/http" |
| 8 | "net/http/httptest" |
| 9 | "strings" |
| 10 | "testing" |
| 11 | |
| 12 | "amadan.net/oficina/calls/protocol" |
| 13 | ) |
| 14 | |
| 15 | func TestShareInvitationAcceptsOnlySameServerInvitationURLs(t *testing.T) { |
| 16 | const token = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" |
| 17 | for _, shape := range []string{"valid", "foreign", "room", "fragment", "refused"} { |
| 18 | t.Run(shape, func(t *testing.T) { |
| 19 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { |
| 20 | if req.Method != http.MethodPost || req.URL.Path != "/m/room/share" || req.Header.Get("Accept") != "application/json" { |
| 21 | t.Error("wrong invitation request") |
| 22 | } |
| 23 | origin := req.Header.Get("Origin") |
| 24 | link := origin + "/j/" + token |
| 25 | switch shape { |
| 26 | case "foreign": |
| 27 | link = "https://elsewhere.example/j/" + token |
| 28 | case "room": |
| 29 | link = origin + "/m/room" |
| 30 | case "fragment": |
| 31 | link += "#unexpected" |
| 32 | case "refused": |
| 33 | w.WriteHeader(http.StatusForbidden) |
| 34 | } |
| 35 | json.NewEncoder(w).Encode(map[string]string{"url": link}) |
| 36 | })) |
| 37 | defer srv.Close() |
| 38 | r, err := New(srv.URL, "room", srv.Client(), func(Event) {}) |
| 39 | if err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | defer r.Close() |
| 43 | link, err := r.ShareLink() |
| 44 | if shape == "valid" { |
| 45 | if err != nil || link != srv.URL+"/j/"+token { |
| 46 | t.Fatalf("valid invitation: %v", err) |
| 47 | } |
| 48 | } else if err == nil || link != "" { |
| 49 | t.Fatal("invalid or refused invitation accepted") |
| 50 | } |
| 51 | }) |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | func TestSSEFramingAndBounds(t *testing.T) { |
| 56 | for _, tc := range []struct { |
| 57 | name, input string |
| 58 | want int |
| 59 | bad bool |
| 60 | }{ |
| 61 | {"comments and multiline", ": heartbeat\r\nevent: hello\r\ndata: {\"type\":\"hello\",\r\ndata: \"peer\":\"abc\"}\r\n\r\n", 1, false}, |
| 62 | {"incomplete event", "data: {\"type\":\"hello\"}\n", 0, false}, |
| 63 | {"malformed", "data: {bad}\n\n", 0, true}, |
| 64 | {"bounded line", "data: " + strings.Repeat("x", maxResponse) + "\n\n", 0, true}, |
| 65 | {"bounded event", strings.Repeat("data: "+strings.Repeat("x", 1024)+"\n", 130) + "\n", 0, true}, |
| 66 | } { |
| 67 | t.Run(tc.name, func(t *testing.T) { |
| 68 | count := 0 |
| 69 | err := readEvents(strings.NewReader(tc.input), func(m protocol.Message) error { count++; return nil }) |
| 70 | if count != tc.want || (err != nil && !errors.Is(err, io.EOF)) != tc.bad { |
| 71 | t.Fatalf("events=%d err=%v", count, err) |
| 72 | } |
| 73 | }) |
| 74 | } |
| 75 | } |
| 76 | func TestTransportNeverFollowsRedirectWithSession(t *testing.T) { |
| 77 | reached := false |
| 78 | foreign := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reached = true })) |
| 79 | defer foreign.Close() |
| 80 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, foreign.URL, 307) })) |
| 81 | defer server.Close() |
| 82 | tr, err := newTransport(server.URL, "room", server.Client()) |
| 83 | if err != nil { |
| 84 | t.Fatal(err) |
| 85 | } |
| 86 | _, err = tr.request(t.Context(), http.MethodGet, "ice", nil, nil) |
| 87 | var refusal *HTTPError |
| 88 | if !errors.As(err, &refusal) || refusal.Status != 307 || reached { |
| 89 | t.Fatalf("redirect escaped: %v reached=%v", err, reached) |
| 90 | } |
| 91 | } |
| 92 | func TestTransportRejectsUntrustedOriginsAndPaths(t *testing.T) { |
| 93 | for _, origin := range []string{"http://calls.example.com", "https://user:pass@calls.example.com", "https://calls.example.com/path", "https://calls.example.com?token=secret", "https://calls.example.com#fragment", "file:///tmp/a"} { |
| 94 | if _, err := newTransport(origin, "room", http.DefaultClient); err == nil { |
| 95 | t.Fatalf("accepted %s", origin) |
| 96 | } |
| 97 | } |
| 98 | for _, room := range []string{"../other", "a/b", "a?b", "", "a%2fb"} { |
| 99 | if _, err := newTransport("https://calls.example.com", room, http.DefaultClient); err == nil { |
| 100 | t.Fatalf("accepted room %q", room) |
| 101 | } |
| 102 | } |
| 103 | } |
| 104 | |