Skip to content

Commit c8bb1bd

Browse files
author
Jay Conrod
committed
module: allow leading dots in import path elements
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 <jayconrod@google.com> Run-TryBot: Jay Conrod <jayconrod@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Bryan C. Mills <bcmills@google.com>
1 parent 66bf157 commit c8bb1bd

File tree

2 files changed

+28
-18
lines changed

2 files changed

+28
-18
lines changed

module/module.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ func fileNameOK(r rune) bool {
270270

271271
// CheckPath checks that a module path is valid.
272272
// A valid module path is a valid import path, as checked by CheckImportPath,
273-
// with two additional constraints.
273+
// with three additional constraints.
274274
// First, the leading path element (up to the first slash, if any),
275275
// by convention a domain name, must contain only lower-case ASCII letters,
276276
// ASCII digits, dots (U+002E), and dashes (U+002D);
@@ -280,8 +280,9 @@ func fileNameOK(r rune) bool {
280280
// and must not contain any dots. For paths beginning with "gopkg.in/",
281281
// this second requirement is replaced by a requirement that the path
282282
// follow the gopkg.in server's conventions.
283+
// Third, no path element may begin with a dot.
283284
func CheckPath(path string) error {
284-
if err := checkPath(path, false); err != nil {
285+
if err := checkPath(path, modulePath); err != nil {
285286
return fmt.Errorf("malformed module path %q: %v", path, err)
286287
}
287288
i := strings.Index(path, "/")
@@ -315,7 +316,7 @@ func CheckPath(path string) error {
315316
//
316317
// A valid path element is a non-empty string made up of
317318
// ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
318-
// It must not begin or end with a dot (U+002E), nor contain two dots in a row.
319+
// It must not end with a dot (U+002E), nor contain two dots in a row.
319320
//
320321
// The element prefix up to the first dot must not be a reserved file name
321322
// on Windows, regardless of case (CON, com1, NuL, and so on). The element
@@ -326,19 +327,29 @@ func CheckPath(path string) error {
326327
// top-level package documentation for additional information about
327328
// subtleties of Unicode.
328329
func CheckImportPath(path string) error {
329-
if err := checkPath(path, false); err != nil {
330+
if err := checkPath(path, importPath); err != nil {
330331
return fmt.Errorf("malformed import path %q: %v", path, err)
331332
}
332333
return nil
333334
}
334335

336+
// pathKind indicates what kind of path we're checking. Module paths,
337+
// import paths, and file paths have different restrictions.
338+
type pathKind int
339+
340+
const (
341+
modulePath pathKind = iota
342+
importPath
343+
filePath
344+
)
345+
335346
// checkPath checks that a general path is valid.
336347
// It returns an error describing why but not mentioning path.
337348
// Because these checks apply to both module paths and import paths,
338349
// the caller is expected to add the "malformed ___ path %q: " prefix.
339350
// fileName indicates whether the final element of the path is a file name
340351
// (as opposed to a directory name).
341-
func checkPath(path string, fileName bool) error {
352+
func checkPath(path string, kind pathKind) error {
342353
if !utf8.ValidString(path) {
343354
return fmt.Errorf("invalid UTF-8")
344355
}
@@ -357,35 +368,34 @@ func checkPath(path string, fileName bool) error {
357368
elemStart := 0
358369
for i, r := range path {
359370
if r == '/' {
360-
if err := checkElem(path[elemStart:i], fileName); err != nil {
371+
if err := checkElem(path[elemStart:i], kind); err != nil {
361372
return err
362373
}
363374
elemStart = i + 1
364375
}
365376
}
366-
if err := checkElem(path[elemStart:], fileName); err != nil {
377+
if err := checkElem(path[elemStart:], kind); err != nil {
367378
return err
368379
}
369380
return nil
370381
}
371382

372383
// checkElem checks whether an individual path element is valid.
373-
// fileName indicates whether the element is a file name (not a directory name).
374-
func checkElem(elem string, fileName bool) error {
384+
func checkElem(elem string, kind pathKind) error {
375385
if elem == "" {
376386
return fmt.Errorf("empty path element")
377387
}
378388
if strings.Count(elem, ".") == len(elem) {
379389
return fmt.Errorf("invalid path element %q", elem)
380390
}
381-
if elem[0] == '.' && !fileName {
391+
if elem[0] == '.' && kind == modulePath {
382392
return fmt.Errorf("leading dot in path element")
383393
}
384394
if elem[len(elem)-1] == '.' {
385395
return fmt.Errorf("trailing dot in path element")
386396
}
387397
charOK := pathOK
388-
if fileName {
398+
if kind == filePath {
389399
charOK = fileNameOK
390400
}
391401
for _, r := range elem {
@@ -406,7 +416,7 @@ func checkElem(elem string, fileName bool) error {
406416
}
407417
}
408418

409-
if fileName {
419+
if kind == filePath {
410420
// don't check for Windows short-names in file names. They're
411421
// only an issue for import paths.
412422
return nil
@@ -444,7 +454,7 @@ func checkElem(elem string, fileName bool) error {
444454
// top-level package documentation for additional information about
445455
// subtleties of Unicode.
446456
func CheckFilePath(path string) error {
447-
if err := checkPath(path, true); err != nil {
457+
if err := checkPath(path, filePath); err != nil {
448458
return fmt.Errorf("malformed file path %q: %v", path, err)
449459
}
450460
return nil
@@ -647,7 +657,7 @@ func EscapePath(path string) (escaped string, err error) {
647657
// Versions are allowed to be in non-semver form but must be valid file names
648658
// and not contain exclamation marks.
649659
func EscapeVersion(v string) (escaped string, err error) {
650-
if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
660+
if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
651661
return "", &InvalidVersionError{
652662
Version: v,
653663
Err: fmt.Errorf("disallowed version string"),
@@ -706,7 +716,7 @@ func UnescapeVersion(escaped string) (v string, err error) {
706716
if !ok {
707717
return "", fmt.Errorf("invalid escaped version %q", escaped)
708718
}
709-
if err := checkElem(v, true); err != nil {
719+
if err := checkElem(v, filePath); err != nil {
710720
return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
711721
}
712722
return v, nil

module/module_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ var checkPathTests = []struct {
7878
{"x.y/\xFFz", false, false, false},
7979
{"/x.y/z", false, false, false},
8080
{"x./z", false, false, false},
81-
{".x/z", false, false, true},
81+
{".x/z", false, true, true},
8282
{"-x/z", false, false, false},
8383
{"x..y/z", true, true, true},
8484
{"x.y/z/../../w", false, false, false},
@@ -184,8 +184,8 @@ var checkPathTests = []struct {
184184
{"./y", false, false, false},
185185
{"x:y", false, false, false},
186186
{`\temp\foo`, false, false, false},
187-
{".gitignore", false, false, true},
188-
{".github/ISSUE_TEMPLATE", false, false, true},
187+
{".gitignore", false, true, true},
188+
{".github/ISSUE_TEMPLATE", false, true, true},
189189
{"x☺y", false, false, false},
190190
}
191191

0 commit comments

Comments
 (0)