Usage:
- go get [-d] [-f] [-fix] [-t] [-u] [build flags] [packages]
+ go get [-d] [-f] [-fix] [-insecure] [-t] [-u] [build flags] [packages]
Get downloads and installs the packages named by the import paths,
along with their dependencies.
The -fix flag instructs get to run the fix tool on the downloaded packages
before resolving dependencies or building the code.
+The -insecure flag permits fetching from repositories and resolving
+custom domains using insecure schemes such as HTTP. Use with caution.
+
The -t flag instructs get to also download the packages required to build
the tests for the specified packages.
return nil, errHTTP
}
-func httpsOrHTTP(importPath string) (string, io.ReadCloser, error) {
+func httpsOrHTTP(importPath string, security securityMode) (string, io.ReadCloser, error) {
return "", nil, errHTTP
}
)
var cmdGet = &Command{
- UsageLine: "get [-d] [-f] [-fix] [-t] [-u] [build flags] [packages]",
+ UsageLine: "get [-d] [-f] [-fix] [-insecure] [-t] [-u] [build flags] [packages]",
Short: "download and install packages and dependencies",
Long: `
Get downloads and installs the packages named by the import paths,
The -fix flag instructs get to run the fix tool on the downloaded packages
before resolving dependencies or building the code.
+The -insecure flag permits fetching from repositories and resolving
+custom domains using insecure schemes such as HTTP. Use with caution.
+
The -t flag instructs get to also download the packages required to build
the tests for the specified packages.
var getT = cmdGet.Flag.Bool("t", false, "")
var getU = cmdGet.Flag.Bool("u", false, "")
var getFix = cmdGet.Flag.Bool("fix", false, "")
+var getInsecure = cmdGet.Flag.Bool("insecure", false, "")
func init() {
addBuildFlags(cmdGet)
repo, rootPath string
err error
)
+
+ security := secure
+ if *getInsecure {
+ security = insecure
+ }
+
if p.build.SrcRoot != "" {
// Directory exists. Look for checkout along path to src.
vcs, rootPath, err = vcsForDir(p)
repo = "<local>" // should be unused; make distinctive
// Double-check where it came from.
- if *getU && vcs.remoteRepo != nil && !*getF {
+ if *getU && vcs.remoteRepo != nil {
dir := filepath.Join(p.build.SrcRoot, rootPath)
if remote, err := vcs.remoteRepo(vcs, dir); err == nil {
- if rr, err := repoRootForImportPath(p.ImportPath); err == nil {
- repo := rr.repo
- if rr.vcs.resolveRepo != nil {
- resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
- if err == nil {
- repo = resolved
+ repo = remote
+
+ if !*getF {
+ if rr, err := repoRootForImportPath(p.ImportPath, security); err == nil {
+ repo := rr.repo
+ if rr.vcs.resolveRepo != nil {
+ resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
+ if err == nil {
+ repo = resolved
+ }
+ }
+ if remote != repo {
+ return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.root, repo, dir, remote)
}
- }
- if remote != repo {
- return fmt.Errorf("%s is a custom import path for %s, but %s is checked out from %s", rr.root, repo, dir, remote)
}
}
}
} else {
// Analyze the import path to determine the version control system,
// repository, and the import path for the root of the repository.
- rr, err := repoRootForImportPath(p.ImportPath)
+ rr, err := repoRootForImportPath(p.ImportPath, security)
if err != nil {
return err
}
vcs, repo, rootPath = rr.vcs, rr.repo, rr.root
}
+ if !vcs.isSecure(repo) && !*getInsecure {
+ return fmt.Errorf("cannot download, %v uses insecure protocol", repo)
+ }
if p.build.SrcRoot == "" {
// Package not found. Put in first directory of $GOPATH.
}
}
+// failSSH puts an ssh executable in the PATH that always fails.
+// This is to stub out uses of ssh by go get.
+func (tg *testgoData) failSSH() {
+ wd, err := os.Getwd()
+ if err != nil {
+ tg.t.Fatal(err)
+ }
+ fail := filepath.Join(wd, "testdata/failssh")
+ tg.setenv("PATH", fmt.Sprintf("%v%c%v", fail, filepath.ListSeparator, os.Getenv("PATH")))
+}
+
func TestFileLineInErrorMessages(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tg.runFail("build", "y")
tg.grepBoth("is a program", `did not find expected error message ("is a program")`)
}
+
+func TestGoGetInsecure(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ tg := testgo(t)
+ defer tg.cleanup()
+ tg.makeTempdir()
+ tg.setenv("GOPATH", tg.path("."))
+ tg.failSSH()
+
+ const repo = "wh3rd.net/git.git"
+
+ // Try go get -d of HTTP-only repo (should fail).
+ tg.runFail("get", "-d", repo)
+
+ // Try again with -insecure (should succeed).
+ tg.run("get", "-d", "-insecure", repo)
+
+ // Try updating without -insecure (should fail).
+ tg.runFail("get", "-d", "-u", "-f", repo)
+}
+
+func TestGoGetUpdateInsecure(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ tg := testgo(t)
+ defer tg.cleanup()
+ tg.makeTempdir()
+ tg.setenv("GOPATH", tg.path("."))
+
+ const repo = "github.com/golang/example"
+
+ // Clone the repo via HTTP manually.
+ cmd := exec.Command("git", "clone", "-q", "http://"+repo, tg.path("src/"+repo))
+ if out, err := cmd.CombinedOutput(); err != nil {
+ t.Fatalf("cloning %v repo: %v\n%s", repo, err, out)
+ }
+
+ // Update without -insecure should fail.
+ // Update with -insecure should succeed.
+ // We need -f to ignore import comments.
+ const pkg = repo + "/hello"
+ tg.runFail("get", "-d", "-u", "-f", pkg)
+ tg.run("get", "-d", "-u", "-f", "-insecure", pkg)
+}
+
+func TestGoGetInsecureCustomDomain(t *testing.T) {
+ testenv.MustHaveExternalNetwork(t)
+
+ tg := testgo(t)
+ defer tg.cleanup()
+ tg.makeTempdir()
+ tg.setenv("GOPATH", tg.path("."))
+
+ const repo = "wh3rd.net/repo"
+ tg.runFail("get", "-d", repo)
+ tg.run("get", "-d", "-insecure", repo)
+}
"log"
"net/http"
"net/url"
+ "time"
)
// httpClient is the default HTTP client, but a variable so it can be
// changed by tests, without modifying http.DefaultClient.
var httpClient = http.DefaultClient
+var impatientHTTPClient = &http.Client{
+ Timeout: time.Duration(5 * time.Second),
+}
type httpError struct {
status string
// httpsOrHTTP returns the body of either the importPath's
// https resource or, if unavailable, the http resource.
-func httpsOrHTTP(importPath string) (urlStr string, body io.ReadCloser, err error) {
+func httpsOrHTTP(importPath string, security securityMode) (urlStr string, body io.ReadCloser, err error) {
fetch := func(scheme string) (urlStr string, res *http.Response, err error) {
u, err := url.Parse(scheme + "://" + importPath)
if err != nil {
if buildV {
log.Printf("Fetching %s", urlStr)
}
- res, err = httpClient.Get(urlStr)
+ if security == insecure && scheme == "https" { // fail earlier
+ res, err = impatientHTTPClient.Get(urlStr)
+ } else {
+ res, err = httpClient.Get(urlStr)
+ }
return
}
closeBody := func(res *http.Response) {
}
}
closeBody(res)
- urlStr, res, err = fetch("http")
+ if security == insecure {
+ urlStr, res, err = fetch("http")
+ }
}
if err != nil {
closeBody(res)
--- /dev/null
+#!/bin/sh
+exit 1
"fmt"
"internal/singleflight"
"log"
+ "net/url"
"os"
"os/exec"
"path/filepath"
resolveRepo func(v *vcsCmd, rootDir, remoteRepo string) (realRepo string, err error)
}
+var isSecureScheme = map[string]bool{
+ "https": true,
+ "git+ssh": true,
+ "bzr+ssh": true,
+ "svn+ssh": true,
+}
+
+func (v *vcsCmd) isSecure(repo string) bool {
+ u, err := url.Parse(repo)
+ if err != nil {
+ // If repo is not a URL, it's not secure.
+ return false
+ }
+ return isSecureScheme[u.Scheme]
+}
+
// A tagCmd describes a command to list available tags
// that can be passed to tagSyncCmd.
type tagCmd struct {
if err != nil {
return "", err
}
- repoUrl := strings.TrimSpace(string(outb))
+ repoURL, err := url.Parse(strings.TrimSpace(string(outb)))
+ if err != nil {
+ return "", err
+ }
+
+ // Iterate over insecure schemes too, because this function simply
+ // reports the state of the repo. If we can't see insecure schemes then
+ // we can't report the actual repo URL.
for _, s := range vcsGit.scheme {
- if strings.HasPrefix(repoUrl, s) {
- return repoUrl, nil
+ if repoURL.Scheme == s {
+ return repoURL.String(), nil
}
}
return "", errParse
var httpPrefixRE = regexp.MustCompile(`^https?:`)
+// securityMode specifies whether a function should make network
+// calls using insecure transports (eg, plain text HTTP).
+// The zero value is "secure".
+type securityMode int
+
+const (
+ secure securityMode = iota
+ insecure
+)
+
// repoRootForImportPath analyzes importPath to determine the
// version control system, and code repository to use.
-func repoRootForImportPath(importPath string) (*repoRoot, error) {
- rr, err := repoRootForImportPathStatic(importPath, "")
+func repoRootForImportPath(importPath string, security securityMode) (*repoRoot, error) {
+ rr, err := repoRootForImportPathStatic(importPath, "", security)
if err == errUnknownSite {
// If there are wildcards, look up the thing before the wildcard,
// hoping it applies to the wildcarded parts too.
if i := strings.Index(lookup, "/.../"); i >= 0 {
lookup = lookup[:i]
}
- rr, err = repoRootForImportDynamic(lookup)
+ rr, err = repoRootForImportDynamic(lookup, security)
// repoRootForImportDynamic returns error detail
// that is irrelevant if the user didn't intend to use a
// containing its VCS type (foo.com/repo.git/dir)
//
// If scheme is non-empty, that scheme is forced.
-func repoRootForImportPathStatic(importPath, scheme string) (*repoRoot, error) {
+func repoRootForImportPathStatic(importPath, scheme string, security securityMode) (*repoRoot, error) {
// A common error is to use https://packagepath because that's what
// hg and git require. Diagnose this helpfully.
if loc := httpPrefixRE.FindStringIndex(importPath); loc != nil {
match["repo"] = scheme + "://" + match["repo"]
} else {
for _, scheme := range vcs.scheme {
+ if security == secure && !isSecureScheme[scheme] {
+ continue
+ }
if vcs.ping(scheme, match["repo"]) == nil {
match["repo"] = scheme + "://" + match["repo"]
break
// statically known by repoRootForImportPathStatic.
//
// This handles custom import paths like "name.tld/pkg/foo".
-func repoRootForImportDynamic(importPath string) (*repoRoot, error) {
+func repoRootForImportDynamic(importPath string, security securityMode) (*repoRoot, error) {
slash := strings.Index(importPath, "/")
if slash < 0 {
return nil, errors.New("import path does not contain a slash")
if !strings.Contains(host, ".") {
return nil, errors.New("import path does not begin with hostname")
}
- urlStr, body, err := httpsOrHTTP(importPath)
+ urlStr, body, err := httpsOrHTTP(importPath, security)
if err != nil {
- return nil, fmt.Errorf("http/https fetch: %v", err)
+ msg := "https fetch: %v"
+ if security == insecure {
+ msg = "http/" + msg
+ }
+ return nil, fmt.Errorf(msg, err)
}
defer body.Close()
imports, err := parseMetaGoImports(body)
}
urlStr0 := urlStr
var imports []metaImport
- urlStr, imports, err = metaImportsForPrefix(mmi.Prefix)
+ urlStr, imports, err = metaImportsForPrefix(mmi.Prefix, security)
if err != nil {
return nil, err
}
// It is an error if no imports are found.
// urlStr will still be valid if err != nil.
// The returned urlStr will be of the form "https://golang.org/x/tools?go-get=1"
-func metaImportsForPrefix(importPrefix string) (urlStr string, imports []metaImport, err error) {
+func metaImportsForPrefix(importPrefix string, security securityMode) (urlStr string, imports []metaImport, err error) {
setCache := func(res fetchResult) (fetchResult, error) {
fetchCacheMu.Lock()
defer fetchCacheMu.Unlock()
}
fetchCacheMu.Unlock()
- urlStr, body, err := httpsOrHTTP(importPrefix)
+ urlStr, body, err := httpsOrHTTP(importPrefix, security)
if err != nil {
return setCache(fetchResult{urlStr: urlStr, err: fmt.Errorf("fetch %s: %v", urlStr, err)})
}
}
for _, test := range tests {
- got, err := repoRootForImportPath(test.path)
+ got, err := repoRootForImportPath(test.path, secure)
want := test.want
if want == nil {