Path: blob/dev/pkg/protocols/websocket/websocket_test.go
4538 views
package websocket12import (3"net/url"4"testing"56"github.com/stretchr/testify/require"7urlutil "github.com/projectdiscovery/utils/url"8)910// resolveAddress mirrors the path resolution logic in executeRequestWithPayloads.11// it parses the template address and the input URL then applies the path rule.12func resolveAddress(templateAddress, inputURL string) (string, error) {13parsedAddress, err := url.Parse(templateAddress)14if err != nil {15return "", err16}17parsed, err := urlutil.Parse(inputURL)18if err != nil {19return "", err20}21if parsedAddress.Path == "" || parsedAddress.Path == "/" {22parsedAddress.Path = parsed.Path23}24return parsedAddress.String(), nil25}2627func TestAddressResolution(t *testing.T) {28tests := []struct {29name string30templateAddress string31inputURL string32expected string33}{34// template already has a path so we keep it and don't double35{36name: "same path in both - no doubling",37templateAddress: "wss://jenkins.cloud/cli/ws",38inputURL: "https://jenkins.cloud/cli/ws",39expected: "wss://jenkins.cloud/cli/ws",40},41{42name: "different paths - template path preserved",43templateAddress: "wss://example.com/ws/connect",44inputURL: "https://example.com/api/v1",45expected: "wss://example.com/ws/connect",46},47{48name: "deep template path preserved",49templateAddress: "wss://example.com/a/b/c/d",50inputURL: "https://example.com/x/y",51expected: "wss://example.com/a/b/c/d",52},53{54name: "template path with trailing slash preserved",55templateAddress: "wss://example.com/ws/",56inputURL: "https://example.com/other",57expected: "wss://example.com/ws/",58},5960// when the template has no path we fall back to the input path61{62name: "no template path - input path used",63templateAddress: "wss://example.com",64inputURL: "https://example.com/api/ws",65expected: "wss://example.com/api/ws",66},67{68name: "root template path - input path used",69templateAddress: "wss://example.com/",70inputURL: "https://example.com/chat/ws",71expected: "wss://example.com/chat/ws",72},73{74name: "no paths on either side",75templateAddress: "wss://example.com",76inputURL: "https://example.com",77expected: "wss://example.com",78},79{80name: "root template, root input",81templateAddress: "wss://example.com/",82inputURL: "https://example.com/",83expected: "wss://example.com/",84},8586// ports should not affect path resolution87{88name: "template with port and path",89templateAddress: "wss://example.com:8443/ws",90inputURL: "https://example.com:8443/api",91expected: "wss://example.com:8443/ws",92},93{94name: "template with port, no path - input path used",95templateAddress: "ws://example.com:9090",96inputURL: "http://example.com:9090/stream",97expected: "ws://example.com:9090/stream",98},99{100name: "ws scheme with port and deep input path",101templateAddress: "ws://example.com:8080",102inputURL: "http://example.com:8080/api/v2/ws",103expected: "ws://example.com:8080/api/v2/ws",104},105106// query strings should stay with their respective URLs107{108name: "template with query string preserved",109templateAddress: "wss://example.com/ws?token=abc",110inputURL: "https://example.com/other",111expected: "wss://example.com/ws?token=abc",112},113{114name: "input query string not leaked when template has path",115templateAddress: "wss://example.com/ws",116inputURL: "https://example.com/api?key=secret",117expected: "wss://example.com/ws",118},119{120name: "no template path - input path used but not query",121templateAddress: "wss://example.com",122inputURL: "https://example.com/stream?v=1",123expected: "wss://example.com/stream",124},125126// both ws and wss schemes should behave the same way127{128name: "ws scheme template path preserved",129templateAddress: "ws://example.com/plain",130inputURL: "http://example.com/other",131expected: "ws://example.com/plain",132},133{134name: "wss scheme no path - input path used",135templateAddress: "wss://secure.example.com",136inputURL: "https://secure.example.com/endpoint",137expected: "wss://secure.example.com/endpoint",138},139140// same logic applies to IP-based targets141{142name: "IPv4 template with path",143templateAddress: "ws://192.168.1.1/ws",144inputURL: "http://192.168.1.1/api",145expected: "ws://192.168.1.1/ws",146},147{148name: "IPv4 template no path - input path used",149templateAddress: "ws://192.168.1.1",150inputURL: "http://192.168.1.1/metrics/ws",151expected: "ws://192.168.1.1/metrics/ws",152},153{154name: "IPv4 with port, no path",155templateAddress: "ws://10.0.0.1:3000",156inputURL: "http://10.0.0.1:3000/graphql/ws",157expected: "ws://10.0.0.1:3000/graphql/ws",158},159160// patterns from actual templates and bug reports161{162name: "jenkins websocket - the original bug report",163templateAddress: "wss://jenkins-ci.corp.cloud/cli/ws",164inputURL: "https://jenkins-ci.corp.cloud/cli/ws",165expected: "wss://jenkins-ci.corp.cloud/cli/ws",166},167{168name: "grafana live websocket",169templateAddress: "wss://grafana.local/api/live/ws",170inputURL: "https://grafana.local/api/live/ws",171expected: "wss://grafana.local/api/live/ws",172},173{174name: "generic host-only template with target path",175templateAddress: "wss://target.example.com",176inputURL: "https://target.example.com/socket.io/ws",177expected: "wss://target.example.com/socket.io/ws",178},179}180181for _, tt := range tests {182t.Run(tt.name, func(t *testing.T) {183result, err := resolveAddress(tt.templateAddress, tt.inputURL)184require.NoError(t, err)185require.Equal(t, tt.expected, result)186})187}188}189190func TestGetAddress(t *testing.T) {191tests := []struct {192name string193input string194want string195wantErr bool196}{197{198name: "ws scheme returns host",199input: "ws://example.com/path",200want: "example.com",201},202{203name: "wss scheme returns host with port",204input: "wss://example.com:8443/path",205want: "example.com:8443",206},207{208name: "ws with non-standard port",209input: "ws://example.com:9090",210want: "example.com:9090",211},212{213name: "wss with standard port",214input: "wss://example.com:443/ws",215want: "example.com:443",216},217{218name: "ws IPv4",219input: "ws://192.168.1.1/ws",220want: "192.168.1.1",221},222{223name: "ws IPv4 with port",224input: "ws://192.168.1.1:8080/ws",225want: "192.168.1.1:8080",226},227{228name: "http scheme rejected",229input: "http://example.com",230wantErr: true,231},232{233name: "https scheme rejected",234input: "https://example.com",235wantErr: true,236},237{238name: "ftp scheme rejected",239input: "ftp://example.com",240wantErr: true,241},242{243name: "invalid URL",244input: "://broken",245wantErr: true,246},247}248249for _, tt := range tests {250t.Run(tt.name, func(t *testing.T) {251got, err := getAddress(tt.input)252if tt.wantErr {253require.Error(t, err)254return255}256require.NoError(t, err)257require.Equal(t, tt.want, got)258})259}260}261262263