package callscore import ( "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "amadan.net/oficina/calls/protocol" ) func TestShareInvitationAcceptsOnlySameServerInvitationURLs(t *testing.T) { const token = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" for _, shape := range []string{"valid", "foreign", "room", "fragment", "refused"} { t.Run(shape, func(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost || req.URL.Path != "/m/room/share" || req.Header.Get("Accept") != "application/json" { t.Error("wrong invitation request") } origin := req.Header.Get("Origin") link := origin + "/j/" + token switch shape { case "foreign": link = "https://elsewhere.example/j/" + token case "room": link = origin + "/m/room" case "fragment": link += "#unexpected" case "refused": w.WriteHeader(http.StatusForbidden) } json.NewEncoder(w).Encode(map[string]string{"url": link}) })) defer srv.Close() r, err := New(srv.URL, "room", srv.Client(), func(Event) {}) if err != nil { t.Fatal(err) } defer r.Close() link, err := r.ShareLink() if shape == "valid" { if err != nil || link != srv.URL+"/j/"+token { t.Fatalf("valid invitation: %v", err) } } else if err == nil || link != "" { t.Fatal("invalid or refused invitation accepted") } }) } } func TestSSEFramingAndBounds(t *testing.T) { for _, tc := range []struct { name, input string want int bad bool }{ {"comments and multiline", ": heartbeat\r\nevent: hello\r\ndata: {\"type\":\"hello\",\r\ndata: \"peer\":\"abc\"}\r\n\r\n", 1, false}, {"incomplete event", "data: {\"type\":\"hello\"}\n", 0, false}, {"malformed", "data: {bad}\n\n", 0, true}, {"bounded line", "data: " + strings.Repeat("x", maxResponse) + "\n\n", 0, true}, {"bounded event", strings.Repeat("data: "+strings.Repeat("x", 1024)+"\n", 130) + "\n", 0, true}, } { t.Run(tc.name, func(t *testing.T) { count := 0 err := readEvents(strings.NewReader(tc.input), func(m protocol.Message) error { count++; return nil }) if count != tc.want || (err != nil && !errors.Is(err, io.EOF)) != tc.bad { t.Fatalf("events=%d err=%v", count, err) } }) } } func TestTransportNeverFollowsRedirectWithSession(t *testing.T) { reached := false foreign := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reached = true })) defer foreign.Close() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, foreign.URL, 307) })) defer server.Close() tr, err := newTransport(server.URL, "room", server.Client()) if err != nil { t.Fatal(err) } _, err = tr.request(t.Context(), http.MethodGet, "ice", nil, nil) var refusal *HTTPError if !errors.As(err, &refusal) || refusal.Status != 307 || reached { t.Fatalf("redirect escaped: %v reached=%v", err, reached) } } func TestTransportRejectsUntrustedOriginsAndPaths(t *testing.T) { 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"} { if _, err := newTransport(origin, "room", http.DefaultClient); err == nil { t.Fatalf("accepted %s", origin) } } for _, room := range []string{"../other", "a/b", "a?b", "", "a%2fb"} { if _, err := newTransport("https://calls.example.com", room, http.DefaultClient); err == nil { t.Fatalf("accepted room %q", room) } } }