From de207b02a1dfd04333e449bcc52a413ae7d8ece9 Mon Sep 17 00:00:00 2001 From: Ingo Oppermann Date: Wed, 22 Mar 2023 15:49:52 +0100 Subject: [PATCH] Fix URL validation if the path contains FFmpeg specific placeholders --- net/url/url.go | 98 +++++++++++++++++++++++++++----- net/url/url_test.go | 132 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 14 deletions(-) diff --git a/net/url/url.go b/net/url/url.go index d99b9ebd..1f89b240 100644 --- a/net/url/url.go +++ b/net/url/url.go @@ -4,25 +4,99 @@ import ( "net" "net/url" "regexp" + "strings" ) -var reScheme = regexp.MustCompile(`(?i)^([a-z][a-z0-9.+-:]*)://`) +type URL struct { + Scheme string + Opaque string // encoded opaque data + User *url.Userinfo // username and password information + Host string // host or host:port + RawPath string // path (relative paths may omit leading slash) + RawQuery string // encoded query values, without '?' + RawFragment string // fragment for references, without '#' +} -// Validate checks whether the given address is a valid URL +func (u *URL) Hostname() string { + if !strings.Contains(u.Host, ":") { + return u.Host + } + + hostname, _, _ := net.SplitHostPort(u.Host) + + return hostname +} + +func (u *URL) Port() string { + if !strings.Contains(u.Host, ":") { + return "" + } + + _, port, _ := net.SplitHostPort(u.Host) + + return port +} + +var reScheme = regexp.MustCompile(`(?i)^([a-z][a-z0-9.+-:]*):/{1,3}`) + +// Validate checks whether the given address is a valid URL, based on the +// relaxed version of Parse in this package. func Validate(address string) error { _, err := Parse(address) return err } -// Parse parses an URL into its components. Returns a net/url.URL or -// an error if the URL couldn't be parsed. -func Parse(address string) (*url.URL, error) { - address = reScheme.ReplaceAllString(address, "//") +// Parse parses an URL into its components. It is a more relaxed version of +// url.Parse as it's not checking the escaping of the path, query, and fragment. +func Parse(address string) (*URL, error) { + address, frag, _ := strings.Cut(address, "#") - u, err := url.Parse(address) + u := &URL{ + RawFragment: frag, + } - return u, err + matches := reScheme.FindStringSubmatch(address) + if matches != nil { + u.Scheme = matches[1] + address = strings.Replace(address, u.Scheme+":", "", 1) + } + + address, query, _ := strings.Cut(address, "?") + u.RawQuery = query + + if strings.HasPrefix(address, "///") { + u.RawPath = strings.TrimPrefix(address, "//") + return u, nil + } + + if strings.HasPrefix(address, "//") { + host, path, _ := strings.Cut(address[2:], "/") + u.RawPath = "/" + path + + parsedHost, err := url.Parse("//" + host) + if err != nil { + return nil, err + } + + u.User = parsedHost.User + u.Host = parsedHost.Host + + return u, nil + } + + if strings.HasPrefix(address, "/") { + u.RawPath = address + + return u, nil + } + + scheme, address, _ := strings.Cut(address, ":") + + u.Scheme = scheme + u.Opaque = address + + return u, nil } // HasScheme returns whether the address has an URL scheme prefix @@ -48,15 +122,11 @@ func Lookup(address string) (string, error) { return "", err } - if len(u.Host) == 0 { + host := u.Hostname() + if len(host) == 0 { return "", nil } - host, _, err := net.SplitHostPort(u.Host) - if err != nil { - host = u.Host - } - addrs, err := net.LookupHost(host) if err != nil { return "", err diff --git a/net/url/url_test.go b/net/url/url_test.go index 460663e7..dff373b8 100644 --- a/net/url/url_test.go +++ b/net/url/url_test.go @@ -36,6 +36,12 @@ func TestValidate(t *testing.T) { err = Validate("foobar") require.NoError(t, err) + + err = Validate("http://localhost/foobar_%25v") + require.NoError(t, err) + + err = Validate("http://localhost/foobar_%v") + require.NoError(t, err) } func TestScheme(t *testing.T) { @@ -48,3 +54,129 @@ func TestScheme(t *testing.T) { r = HasScheme("//localhost/foobar") require.False(t, r) } + +func TestPars(t *testing.T) { + u, err := Parse("http://localhost/foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "", + User: nil, + Host: "localhost", + RawPath: "/foobar", + RawQuery: "", + RawFragment: "", + }, u) + + u, err = Parse("iueriherfd://localhost/foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "iueriherfd", + Opaque: "", + User: nil, + Host: "localhost", + RawPath: "/foobar", + RawQuery: "", + RawFragment: "", + }, u) + + u, err = Parse("//localhost/foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "", + Opaque: "", + User: nil, + Host: "localhost", + RawPath: "/foobar", + RawQuery: "", + RawFragment: "", + }, u) + + u, err = Parse("http://localhost/foobar_%v?foo=bar#foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "", + User: nil, + Host: "localhost", + RawPath: "/foobar_%v", + RawQuery: "foo=bar", + RawFragment: "foobar", + }, u) + + u, err = Parse("http:localhost/foobar_%v?foo=bar#foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "localhost/foobar_%v", + User: nil, + Host: "", + RawPath: "", + RawQuery: "foo=bar", + RawFragment: "foobar", + }, u) + + u, err = Parse("http:/localhost/foobar_%v?foo=bar#foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "", + User: nil, + Host: "", + RawPath: "/localhost/foobar_%v", + RawQuery: "foo=bar", + RawFragment: "foobar", + }, u) + + u, err = Parse("http:///localhost/foobar_%v?foo=bar#foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "", + User: nil, + Host: "", + RawPath: "/localhost/foobar_%v", + RawQuery: "foo=bar", + RawFragment: "foobar", + }, u) + + u, err = Parse("foo:bar://localhost/foobar_%v?foo=bar#foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "foo:bar", + Opaque: "", + User: nil, + Host: "localhost", + RawPath: "/foobar_%v", + RawQuery: "foo=bar", + RawFragment: "foobar", + }, u) + + u, err = Parse("http://localhost:8080/foobar") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "http", + Opaque: "", + User: nil, + Host: "localhost:8080", + RawPath: "/foobar", + RawQuery: "", + RawFragment: "", + }, u) + require.Equal(t, "localhost", u.Hostname()) + require.Equal(t, "8080", u.Port()) + + u, err = Parse("https://www.google.com") + require.NoError(t, err) + require.Equal(t, &URL{ + Scheme: "https", + Opaque: "", + User: nil, + Host: "www.google.com", + RawPath: "/", + RawQuery: "", + RawFragment: "", + }, u) + require.Equal(t, "www.google.com", u.Hostname()) + require.Equal(t, "", u.Port()) +}