Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions pkg/appsec/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,10 +374,7 @@ func NewParsedRequestFromRequest(r *http.Request, logger *log.Entry) (ParsedRequ
r.Header.Del("User-Agent")
}

parsedURL, err := url.Parse(clientURI)
if err != nil {
return ParsedRequest{}, fmt.Errorf("unable to parse url '%s': %s", clientURI, err)
}
parsedURL := exprhelpers.ParseURL(clientURI)

originalHTTPRequest.URL = parsedURL

Expand Down
16 changes: 16 additions & 0 deletions pkg/exprhelpers/waf.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ func ParseQueryIntoValues(m url.Values, query string) {
}
}

// ParseURL is a permissive URL parser that handles edge cases like
// authority-form URIs (host:port) used in CONNECT requests.
// It never returns an error - malformed URLs are parsed best-effort.
func ParseURL(rawURL string) *url.URL {
// Try standard parsing first
if u, err := url.Parse(rawURL); err == nil {
return u
}

// Fallback: store raw value in Opaque to preserve original request-target
// This handles cases like CONNECT authority-form (host:port)
return &url.URL{
Opaque: rawURL,
}
}

func hexDigitToByte(digit byte) (byte, bool) {
switch {
case digit >= '0' && digit <= '9':
Expand Down
52 changes: 52 additions & 0 deletions pkg/exprhelpers/waf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,58 @@ func TestParseQuery(t *testing.T) {
}
}

func TestParseURL(t *testing.T) {
tests := []struct {
name string
rawURL string
expectedPath string
expectedHost string
expectedOpaque string
}{
{
name: "Normal path",
rawURL: "/path/to/resource",
expectedPath: "/path/to/resource",
},
{
name: "Path with query",
rawURL: "/path/to/resource?query=value",
expectedPath: "/path/to/resource",
},
{
name: "Full URL with scheme",
rawURL: "http://example.com/path",
expectedPath: "/path",
expectedHost: "example.com",
},
{
name: "Authority-form (CONNECT request)",
rawURL: "116.202.157.104:80",
expectedOpaque: "116.202.157.104:80",
},
{
name: "Authority-form with hostname (parsed as scheme:opaque)",
rawURL: "example.com:443",
expectedOpaque: "443", // Go parses this as scheme=example.com, opaque=443
},
{
name: "Empty string",
rawURL: "",
expectedPath: "",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := ParseURL(test.rawURL)
require.NotNil(t, res)
require.Equal(t, test.expectedPath, res.Path)
require.Equal(t, test.expectedHost, res.Host)
require.Equal(t, test.expectedOpaque, res.Opaque)
})
}
}

func TestExtractQueryParam(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading