mirror of https://github.com/knative/func.git
151 lines
4.1 KiB
Go
151 lines
4.1 KiB
Go
/*
|
|
* Copyright (c) 2014 Will Maier <wcmaier@m.aier.us>
|
|
*
|
|
* 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"
|
|
)
|
|
|
|
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) {
|
|
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
|
|
}
|