From 45bdbcbe6fdab5a8a4ed4f1b16c191f400a0f6b6 Mon Sep 17 00:00:00 2001 From: "Santiago M. Mola" Date: Wed, 26 Apr 2017 17:04:59 +0200 Subject: transport: make Endpoint an interface, fixes #362 * add internal *url.URL implementation for regular URLs. * add internal implementation for SCP-like URLs. --- plumbing/transport/common.go | 131 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 19 deletions(-) (limited to 'plumbing/transport/common.go') diff --git a/plumbing/transport/common.go b/plumbing/transport/common.go index d3cfe21..0ff9e89 100644 --- a/plumbing/transport/common.go +++ b/plumbing/transport/common.go @@ -18,6 +18,7 @@ import ( "io" "net/url" "regexp" + "strconv" "gopkg.in/src-d/go-git.v4/plumbing" "gopkg.in/src-d/go-git.v4/plumbing/protocol/packp" @@ -28,7 +29,7 @@ var ( ErrRepositoryNotFound = errors.New("repository not found") ErrEmptyRemoteRepository = errors.New("remote repository is empty") ErrAuthenticationRequired = errors.New("authentication required") - ErrAuthorizationFailed = errors.New("authorization failed") + ErrAuthorizationFailed = errors.New("authorization failed") ErrEmptyUploadPackRequest = errors.New("empty git-upload-pack given") ErrInvalidAuthMethod = errors.New("invalid auth method") ErrAlreadyConnected = errors.New("session already established") @@ -88,42 +89,134 @@ type ReceivePackSession interface { ReceivePack(*packp.ReferenceUpdateRequest) (*packp.ReportStatus, error) } -type Endpoint url.URL - -var ( - isSchemeRegExp = regexp.MustCompile("^[^:]+://") - scpLikeUrlRegExp = regexp.MustCompile("^(?P[^@]+@)?(?P[^:]+):/?(?P.+)$") -) +// Endpoint represents a Git URL in any supported protocol. +type Endpoint interface { + // Protocol returns the protocol (e.g. git, https, file). It should never + // return the empty string. + Protocol() string + // User returns the user or an empty string if none is given. + User() string + // Password returns the password or an empty string if none is given. + Password() string + // Host returns the host or an empty string if none is given. + Host() string + // Port returns the port or 0 if there is no port or a default should be + // used. + Port() int + // Path returns the repository path. + Path() string + // String returns a string representation of the Git URL. + String() string +} func NewEndpoint(endpoint string) (Endpoint, error) { - endpoint = transformSCPLikeIfNeeded(endpoint) + if e, ok := parseSCPLike(endpoint); ok { + return e, nil + } u, err := url.Parse(endpoint) if err != nil { - return Endpoint{}, plumbing.NewPermanentError(err) + return nil, plumbing.NewPermanentError(err) } if !u.IsAbs() { - return Endpoint{}, plumbing.NewPermanentError(fmt.Errorf( + return nil, plumbing.NewPermanentError(fmt.Errorf( "invalid endpoint: %s", endpoint, )) } - return Endpoint(*u), nil + return urlEndpoint{u}, nil +} + +type urlEndpoint struct { + *url.URL +} + +func (e urlEndpoint) Protocol() string { return e.URL.Scheme } +func (e urlEndpoint) Host() string { return e.URL.Hostname() } + +func (e urlEndpoint) User() string { + if e.URL.User == nil { + return "" + } + + return e.URL.User.Username() +} + +func (e urlEndpoint) Password() string { + if e.URL.User == nil { + return "" + } + + p, _ := e.URL.User.Password() + return p +} + +func (e urlEndpoint) Port() int { + p := e.URL.Port() + if p == "" { + return 0 + } + + i, err := strconv.Atoi(e.URL.Port()) + if err != nil { + return 0 + } + + return i } -func (e *Endpoint) String() string { - u := url.URL(*e) - return u.String() +func (e urlEndpoint) Path() string { + var res string = e.URL.Path + if e.URL.RawQuery != "" { + res += "?" + e.URL.RawQuery + } + + if e.URL.Fragment != "" { + res += "#" + e.URL.Fragment + } + + return res } -func transformSCPLikeIfNeeded(endpoint string) string { - if !isSchemeRegExp.MatchString(endpoint) && scpLikeUrlRegExp.MatchString(endpoint) { - m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) - return fmt.Sprintf("ssh://%s%s/%s", m[1], m[2], m[3]) +type scpEndpoint struct { + user string + host string + path string +} + +func (e *scpEndpoint) Protocol() string { return "ssh" } +func (e *scpEndpoint) User() string { return e.user } +func (e *scpEndpoint) Password() string { return "" } +func (e *scpEndpoint) Host() string { return e.host } +func (e *scpEndpoint) Port() int { return 22 } +func (e *scpEndpoint) Path() string { return e.path } + +func (e *scpEndpoint) String() string { + var user string + if e.user != "" { + user = fmt.Sprintf("%s@", e.user) + } + + return fmt.Sprintf("%s%s:%s", user, e.host, e.path) +} + +var ( + isSchemeRegExp = regexp.MustCompile("^[^:]+://") + scpLikeUrlRegExp = regexp.MustCompile("^(?:(?P[^@]+)@)?(?P[^:]+):/?(?P.+)$") +) + +func parseSCPLike(endpoint string) (Endpoint, bool) { + if isSchemeRegExp.MatchString(endpoint) || !scpLikeUrlRegExp.MatchString(endpoint) { + return nil, false } - return endpoint + m := scpLikeUrlRegExp.FindStringSubmatch(endpoint) + return &scpEndpoint{ + user: m[1], + host: m[2], + path: m[3], + }, true } // UnsupportedCapabilities are the capabilities not supported by any client -- cgit