Skip to content

Commit

Permalink
module: allow leading dots in import path elements
Browse files Browse the repository at this point in the history
These were always disallowed, but the restriction wasn't enforced in
most cases until Go 1.16. That's broken more projects than we hoped.

This change allows leading dots in import path elements. Leading dots
are still not allowed in module path elements. Leading dots were
always allowed in file path elements. Trailing dots are still
forbidden in all cases.

For golang/go#43985

Change-Id: Id9cf728a341931565ab9e81f600b2341aa178683
Reviewed-on: https://go-review.googlesource.com/c/mod/+/297089
Trust: Jay Conrod <[email protected]>
Run-TryBot: Jay Conrod <[email protected]>
TryBot-Result: Go Bot <[email protected]>
Reviewed-by: Bryan C. Mills <[email protected]>
  • Loading branch information
Jay Conrod committed Mar 1, 2021
1 parent 66bf157 commit c8bb1bd
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 18 deletions.
40 changes: 25 additions & 15 deletions module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ func fileNameOK(r rune) bool {

// CheckPath checks that a module path is valid.
// A valid module path is a valid import path, as checked by CheckImportPath,
// with two additional constraints.
// with three additional constraints.
// First, the leading path element (up to the first slash, if any),
// by convention a domain name, must contain only lower-case ASCII letters,
// ASCII digits, dots (U+002E), and dashes (U+002D);
Expand All @@ -280,8 +280,9 @@ func fileNameOK(r rune) bool {
// and must not contain any dots. For paths beginning with "gopkg.in/",
// this second requirement is replaced by a requirement that the path
// follow the gopkg.in server's conventions.
// Third, no path element may begin with a dot.
func CheckPath(path string) error {
if err := checkPath(path, false); err != nil {
if err := checkPath(path, modulePath); err != nil {
return fmt.Errorf("malformed module path %q: %v", path, err)
}
i := strings.Index(path, "/")
Expand Down Expand Up @@ -315,7 +316,7 @@ func CheckPath(path string) error {
//
// A valid path element is a non-empty string made up of
// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
// It must not begin or end with a dot (U+002E), nor contain two dots in a row.
// It must not end with a dot (U+002E), nor contain two dots in a row.
//
// The element prefix up to the first dot must not be a reserved file name
// on Windows, regardless of case (CON, com1, NuL, and so on). The element
Expand All @@ -326,19 +327,29 @@ func CheckPath(path string) error {
// top-level package documentation for additional information about
// subtleties of Unicode.
func CheckImportPath(path string) error {
if err := checkPath(path, false); err != nil {
if err := checkPath(path, importPath); err != nil {
return fmt.Errorf("malformed import path %q: %v", path, err)
}
return nil
}

// pathKind indicates what kind of path we're checking. Module paths,
// import paths, and file paths have different restrictions.
type pathKind int

const (
modulePath pathKind = iota
importPath
filePath
)

// 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 {
func checkPath(path string, kind pathKind) error {
if !utf8.ValidString(path) {
return fmt.Errorf("invalid UTF-8")
}
Expand All @@ -357,35 +368,34 @@ func checkPath(path string, fileName bool) error {
elemStart := 0
for i, r := range path {
if r == '/' {
if err := checkElem(path[elemStart:i], fileName); err != nil {
if err := checkElem(path[elemStart:i], kind); err != nil {
return err
}
elemStart = i + 1
}
}
if err := checkElem(path[elemStart:], fileName); err != nil {
if err := checkElem(path[elemStart:], kind); 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 {
func checkElem(elem string, kind pathKind) 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 {
if elem[0] == '.' && kind == modulePath {
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 {
if kind == filePath {
charOK = fileNameOK
}
for _, r := range elem {
Expand All @@ -406,7 +416,7 @@ func checkElem(elem string, fileName bool) error {
}
}

if fileName {
if kind == filePath {
// don't check for Windows short-names in file names. They're
// only an issue for import paths.
return nil
Expand Down Expand Up @@ -444,7 +454,7 @@ func checkElem(elem string, fileName bool) error {
// top-level package documentation for additional information about
// subtleties of Unicode.
func CheckFilePath(path string) error {
if err := checkPath(path, true); err != nil {
if err := checkPath(path, filePath); err != nil {
return fmt.Errorf("malformed file path %q: %v", path, err)
}
return nil
Expand Down Expand Up @@ -647,7 +657,7 @@ func EscapePath(path string) (escaped string, err error) {
// Versions are allowed to be in non-semver form but must be valid file names
// and not contain exclamation marks.
func EscapeVersion(v string) (escaped string, err error) {
if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
return "", &InvalidVersionError{
Version: v,
Err: fmt.Errorf("disallowed version string"),
Expand Down Expand Up @@ -706,7 +716,7 @@ func UnescapeVersion(escaped string) (v string, err error) {
if !ok {
return "", fmt.Errorf("invalid escaped version %q", escaped)
}
if err := checkElem(v, true); err != nil {
if err := checkElem(v, filePath); err != nil {
return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
}
return v, nil
Expand Down
6 changes: 3 additions & 3 deletions module/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ var checkPathTests = []struct {
{"x.y/\xFFz", false, false, false},
{"/x.y/z", false, false, false},
{"x./z", false, false, false},
{".x/z", false, false, true},
{".x/z", false, true, true},
{"-x/z", false, false, false},
{"x..y/z", true, true, true},
{"x.y/z/../../w", false, false, false},
Expand Down Expand Up @@ -184,8 +184,8 @@ var checkPathTests = []struct {
{"./y", false, false, false},
{"x:y", false, false, false},
{`\temp\foo`, false, false, false},
{".gitignore", false, false, true},
{".github/ISSUE_TEMPLATE", false, false, true},
{".gitignore", false, true, true},
{".github/ISSUE_TEMPLATE", false, true, true},
{"x☺y", false, false, false},
}

Expand Down

0 comments on commit c8bb1bd

Please sign in to comment.