diff --git a/go.mod b/go.mod index ee8139d1..be6d2374 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( // found in steve. This is used just for testing. github.com/testcontainers/testcontainers-go v0.13.0 github.com/urfave/cli v1.22.14 - github.com/whilp/git-urls v1.0.0 golang.org/x/crypto v0.18.0 gopkg.in/go-playground/webhooks.v5 v5.17.0 gotest.tools v2.2.0+incompatible @@ -39,7 +38,11 @@ require ( sigs.k8s.io/cli-utils v0.33.0 ) -require github.com/rancher/wrangler/v2 v2.1.2 +require ( + github.com/go-playground/webhooks/v6 v6.3.0 + github.com/rancher/wrangler/v2 v2.1.2 + github.com/whilp/git-urls v1.0.0 +) require ( dario.cat/mergo v1.0.0 // indirect @@ -70,7 +73,6 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-playground/webhooks/v6 v6.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/pkg/git-urls/urls.go b/pkg/git-urls/urls.go new file mode 100644 index 00000000..439e2059 --- /dev/null +++ b/pkg/git-urls/urls.go @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014 Will Maier + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* +Package giturls parses Git URLs. + +These URLs include standard RFC 3986 URLs as well as special formats that +are specific to Git. Examples are provided in the Git documentation at +https://www.kernel.org/pub/software/scm/git/docs/git-clone.html. +*/ +package giturls + +import ( + "fmt" + "net/url" + "regexp" + "strings" +) + +// Max length of the scpUrl to prevent reDOS attacks +const maxLen = 2048 + +var ( + // scpSyntax was modified from https://golang.org/src/cmd/go/vcs.go. + scpSyntax = regexp.MustCompile(`^([a-zA-Z0-9-._~]+@)?([a-zA-Z0-9._-]+):([a-zA-Z0-9./._-]+)(?:\?||$)(.*)$`) + + // Transports is a set of known Git URL schemes. + Transports = NewTransportSet( + "ssh", + "git", + "git+ssh", + "http", + "https", + "ftp", + "ftps", + "rsync", + "file", + ) +) + +// Parser converts a string into a URL. +type Parser func(string) (*url.URL, error) + +// Parse parses rawurl into a URL structure. Parse first attempts to +// find a standard URL with a valid Git transport as its scheme. If +// that cannot be found, it then attempts to find a SCP-like URL. And +// if that cannot be found, it assumes rawurl is a local path. If none +// of these rules apply, Parse returns an error. +func Parse(rawurl string) (u *url.URL, err error) { + parsers := []Parser{ + ParseTransport, + ParseScp, + ParseLocal, + } + + // Apply each parser in turn; if the parser succeeds, accept its + // result and return. + for _, p := range parsers { + u, err = p(rawurl) + if err == nil { + return u, err + } + } + + // It's unlikely that none of the parsers will succeed, since + // ParseLocal is very forgiving. + return new(url.URL), fmt.Errorf("failed to parse %q", rawurl) +} + +// ParseTransport parses rawurl into a URL object. Unless the URL's +// scheme is a known Git transport, ParseTransport returns an error. +func ParseTransport(rawurl string) (*url.URL, error) { + u, err := url.Parse(rawurl) + if err == nil && !Transports.Valid(u.Scheme) { + err = fmt.Errorf("scheme %q is not a valid transport", u.Scheme) + } + return u, err +} + +// ParseScp parses rawurl into a URL object. The rawurl must be +// an SCP-like URL, otherwise ParseScp returns an error. +func ParseScp(rawurl string) (*url.URL, error) { + if len(rawurl) > maxLen { + return nil, fmt.Errorf("URL too long: %q", rawurl) + } + match := scpSyntax.FindAllStringSubmatch(rawurl, -1) + if len(match) == 0 { + return nil, fmt.Errorf("no scp URL found in %q", rawurl) + } + m := match[0] + user := strings.TrimRight(m[1], "@") + var userinfo *url.Userinfo + if user != "" { + userinfo = url.User(user) + } + rawquery := "" + if len(m) > 3 { + rawquery = m[4] + } + return &url.URL{ + Scheme: "ssh", + User: userinfo, + Host: m[2], + Path: m[3], + RawQuery: rawquery, + }, nil +} + +// ParseLocal parses rawurl into a URL object with a "file" +// scheme. This will effectively never return an error. +func ParseLocal(rawurl string) (*url.URL, error) { + return &url.URL{ + Scheme: "file", + Host: "", + Path: rawurl, + }, nil +} + +// TransportSet represents a set of valid Git transport schemes. It +// maps these schemes to empty structs, providing a set-like +// interface. +type TransportSet struct { + Transports map[string]struct{} +} + +// NewTransportSet returns a TransportSet with the items keys mapped +// to empty struct values. +func NewTransportSet(items ...string) *TransportSet { + t := &TransportSet{ + Transports: map[string]struct{}{}, + } + for _, i := range items { + t.Transports[i] = struct{}{} + } + return t +} + +// Valid returns true if transport is a known Git URL scheme and false +// if not. +func (t *TransportSet) Valid(transport string) bool { + _, ok := t.Transports[transport] + return ok +}