var downloadRootCache = map[string]bool{}
// download runs the download half of the get command
-// for the package named by the argument.
+// for the package or pattern named by the argument.
func download(arg string, parent *Package, stk *importStack, mode int) {
load := func(path string, mode int) *Package {
if parent == nil {
security = insecure
}
+ // p can be either a real package, or a pseudo-package whose “import path” is
+ // actually a wildcard pattern.
+ // Trim the path at the element containing the first wildcard,
+ // and hope that it applies to the wildcarded parts too.
+ // This makes 'go get rsc.io/pdf/...' work in a fresh GOPATH.
+ importPrefix := p.ImportPath
+ if i := strings.Index(importPrefix, "..."); i >= 0 {
+ slash := strings.LastIndexByte(importPrefix[:i], '/')
+ if slash < 0 {
+ return fmt.Errorf("cannot expand ... in %q", p.ImportPath)
+ }
+ importPrefix = importPrefix[:slash]
+ }
+ if err := CheckImportPath(importPrefix); err != nil {
+ return fmt.Errorf("%s: invalid import path: %v", p.ImportPath, err)
+ }
+
if p.build.SrcRoot != "" {
// Directory exists. Look for checkout along path to src.
vcs, rootPath, err = vcsFromDir(p.Dir, p.build.SrcRoot)
}
repo = remote
if !*getF {
- if rr, err := repoRootForImportPath(p.ImportPath, security); err == nil {
+ if rr, err := repoRootForImportPath(importPrefix, security); err == nil {
repo := rr.repo
if rr.vcs.resolveRepo != nil {
resolved, err := rr.vcs.resolveRepo(rr.vcs, dir, repo)
} 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, security)
+ rr, err := repoRootForImportPath(importPrefix, security)
if err != nil {
return err
}
--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "fmt"
+ "strings"
+ "unicode"
+ "unicode/utf8"
+)
+
+// The following functions are copied verbatim from cmd/go/internal/module/module.go,
+// with one change to additionally reject Windows short-names.
+//
+// TODO(bcmills): After the call site for this function is backported,
+// consolidate this back down to a single copy.
+
+// CheckImportPath checks that an import path is valid.
+func CheckImportPath(path string) error {
+ if err := checkPath(path, false); err != nil {
+ return fmt.Errorf("malformed import path %q: %v", path, err)
+ }
+ return nil
+}
+
+// checkPath checks that a general path is valid.
+// It returns an error describing why but not mentioning path.
+// Because these checks apply to both module paths and import paths,
+// the caller is expected to add the "malformed ___ path %q: " prefix.
+// fileName indicates whether the final element of the path is a file name
+// (as opposed to a directory name).
+func checkPath(path string, fileName bool) error {
+ if !utf8.ValidString(path) {
+ return fmt.Errorf("invalid UTF-8")
+ }
+ if path == "" {
+ return fmt.Errorf("empty string")
+ }
+ if strings.Contains(path, "..") {
+ return fmt.Errorf("double dot")
+ }
+ if strings.Contains(path, "//") {
+ return fmt.Errorf("double slash")
+ }
+ if path[len(path)-1] == '/' {
+ return fmt.Errorf("trailing slash")
+ }
+ elemStart := 0
+ for i, r := range path {
+ if r == '/' {
+ if err := checkElem(path[elemStart:i], fileName); err != nil {
+ return err
+ }
+ elemStart = i + 1
+ }
+ }
+ if err := checkElem(path[elemStart:], fileName); err != nil {
+ return err
+ }
+ return nil
+}
+
+// checkElem checks whether an individual path element is valid.
+// fileName indicates whether the element is a file name (not a directory name).
+func checkElem(elem string, fileName bool) error {
+ if elem == "" {
+ return fmt.Errorf("empty path element")
+ }
+ if strings.Count(elem, ".") == len(elem) {
+ return fmt.Errorf("invalid path element %q", elem)
+ }
+ if elem[0] == '.' && !fileName {
+ return fmt.Errorf("leading dot in path element")
+ }
+ if elem[len(elem)-1] == '.' {
+ return fmt.Errorf("trailing dot in path element")
+ }
+
+ charOK := pathOK
+ if fileName {
+ charOK = fileNameOK
+ }
+ for _, r := range elem {
+ if !charOK(r) {
+ return fmt.Errorf("invalid char %q", r)
+ }
+ }
+
+ // Windows disallows a bunch of path elements, sadly.
+ // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
+ short := elem
+ if i := strings.Index(short, "."); i >= 0 {
+ short = short[:i]
+ }
+ for _, bad := range badWindowsNames {
+ if strings.EqualFold(bad, short) {
+ return fmt.Errorf("disallowed path element %q", elem)
+ }
+ }
+
+ // Reject path components that look like Windows short-names.
+ // Those usually end in a tilde followed by one or more ASCII digits.
+ if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 {
+ suffix := short[tilde+1:]
+ suffixIsDigits := true
+ for _, r := range suffix {
+ if r < '0' || r > '9' {
+ suffixIsDigits = false
+ break
+ }
+ }
+ if suffixIsDigits {
+ return fmt.Errorf("trailing tilde and digits in path element")
+ }
+ }
+
+ return nil
+}
+
+// pathOK reports whether r can appear in an import path element.
+// Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
+// This matches what "go get" has historically recognized in import paths.
+// TODO(rsc): We would like to allow Unicode letters, but that requires additional
+// care in the safe encoding (see note below).
+func pathOK(r rune) bool {
+ if r < utf8.RuneSelf {
+ return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
+ '0' <= r && r <= '9' ||
+ 'A' <= r && r <= 'Z' ||
+ 'a' <= r && r <= 'z'
+ }
+ return false
+}
+
+// fileNameOK reports whether r can appear in a file name.
+// For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
+// If we expand the set of allowed characters here, we have to
+// work harder at detecting potential case-folding and normalization collisions.
+// See note about "safe encoding" below.
+func fileNameOK(r rune) bool {
+ if r < utf8.RuneSelf {
+ // Entire set of ASCII punctuation, from which we remove characters:
+ // ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
+ // We disallow some shell special characters: " ' * < > ? ` |
+ // (Note that some of those are disallowed by the Windows file system as well.)
+ // We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
+ // We allow spaces (U+0020) in file names.
+ const allowed = "!#$%&()+,-.=@[]^_{}~ "
+ if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
+ return true
+ }
+ for i := 0; i < len(allowed); i++ {
+ if rune(allowed[i]) == r {
+ return true
+ }
+ }
+ return false
+ }
+ // It may be OK to add more ASCII punctuation here, but only carefully.
+ // For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
+ return unicode.IsLetter(r)
+}
+
+// badWindowsNames are the reserved file path elements on Windows.
+// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
+var badWindowsNames = []string{
+ "CON",
+ "PRN",
+ "AUX",
+ "NUL",
+ "COM1",
+ "COM2",
+ "COM3",
+ "COM4",
+ "COM5",
+ "COM6",
+ "COM7",
+ "COM8",
+ "COM9",
+ "LPT1",
+ "LPT2",
+ "LPT3",
+ "LPT4",
+ "LPT5",
+ "LPT6",
+ "LPT7",
+ "LPT8",
+ "LPT9",
+}
func repoRootForImportPath(importPath string, security securityMode) (*repoRoot, error) {
rr, err := repoRootFromVCSPaths(importPath, "", security, vcsPaths)
if err == errUnknownSite {
- // If there are wildcards, look up the thing before the wildcard,
- // hoping it applies to the wildcarded parts too.
- // This makes 'go get rsc.io/pdf/...' work in a fresh GOPATH.
- lookup := strings.TrimSuffix(importPath, "/...")
- if i := strings.Index(lookup, "/.../"); i >= 0 {
- lookup = lookup[:i]
- }
- rr, err = repoRootForImportDynamic(lookup, security)
+ rr, err = repoRootForImportDynamic(importPath, security)
if err != nil {
err = fmt.Errorf("unrecognized import path %q (%v)", importPath, err)
}
}
}
+ // Should have been taken care of above, but make sure.
if err == nil && strings.Contains(importPath, "...") && strings.Contains(rr.root, "...") {
// Do not allow wildcards in the repo root.
rr = nil