...

Source file src/cmd/vendor/golang.org/x/mod/module/module.go

Documentation: cmd/vendor/golang.org/x/mod/module

     1  // Copyright 2018 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package module defines the module.Version type along with support code.
     6  //
     7  // The [module.Version] type is a simple Path, Version pair:
     8  //
     9  //	type Version struct {
    10  //		Path string
    11  //		Version string
    12  //	}
    13  //
    14  // There are no restrictions imposed directly by use of this structure,
    15  // but additional checking functions, most notably [Check], verify that
    16  // a particular path, version pair is valid.
    17  //
    18  // # Escaped Paths
    19  //
    20  // Module paths appear as substrings of file system paths
    21  // (in the download cache) and of web server URLs in the proxy protocol.
    22  // In general we cannot rely on file systems to be case-sensitive,
    23  // nor can we rely on web servers, since they read from file systems.
    24  // That is, we cannot rely on the file system to keep rsc.io/QUOTE
    25  // and rsc.io/quote separate. Windows and macOS don't.
    26  // Instead, we must never require two different casings of a file path.
    27  // Because we want the download cache to match the proxy protocol,
    28  // and because we want the proxy protocol to be possible to serve
    29  // from a tree of static files (which might be stored on a case-insensitive
    30  // file system), the proxy protocol must never require two different casings
    31  // of a URL path either.
    32  //
    33  // One possibility would be to make the escaped form be the lowercase
    34  // hexadecimal encoding of the actual path bytes. This would avoid ever
    35  // needing different casings of a file path, but it would be fairly illegible
    36  // to most programmers when those paths appeared in the file system
    37  // (including in file paths in compiler errors and stack traces)
    38  // in web server logs, and so on. Instead, we want a safe escaped form that
    39  // leaves most paths unaltered.
    40  //
    41  // The safe escaped form is to replace every uppercase letter
    42  // with an exclamation mark followed by the letter's lowercase equivalent.
    43  //
    44  // For example,
    45  //
    46  //	github.com/Azure/azure-sdk-for-go ->  github.com/!azure/azure-sdk-for-go.
    47  //	github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
    48  //	github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
    49  //
    50  // Import paths that avoid upper-case letters are left unchanged.
    51  // Note that because import paths are ASCII-only and avoid various
    52  // problematic punctuation (like : < and >), the escaped form is also ASCII-only
    53  // and avoids the same problematic punctuation.
    54  //
    55  // Import paths have never allowed exclamation marks, so there is no
    56  // need to define how to escape a literal !.
    57  //
    58  // # Unicode Restrictions
    59  //
    60  // Today, paths are disallowed from using Unicode.
    61  //
    62  // Although paths are currently disallowed from using Unicode,
    63  // we would like at some point to allow Unicode letters as well, to assume that
    64  // file systems and URLs are Unicode-safe (storing UTF-8), and apply
    65  // the !-for-uppercase convention for escaping them in the file system.
    66  // But there are at least two subtle considerations.
    67  //
    68  // First, note that not all case-fold equivalent distinct runes
    69  // form an upper/lower pair.
    70  // For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
    71  // are three distinct runes that case-fold to each other.
    72  // When we do add Unicode letters, we must not assume that upper/lower
    73  // are the only case-equivalent pairs.
    74  // Perhaps the Kelvin symbol would be disallowed entirely, for example.
    75  // Or perhaps it would escape as "!!k", or perhaps as "(212A)".
    76  //
    77  // Second, it would be nice to allow Unicode marks as well as letters,
    78  // but marks include combining marks, and then we must deal not
    79  // only with case folding but also normalization: both U+00E9 ('é')
    80  // and U+0065 U+0301 ('e' followed by combining acute accent)
    81  // look the same on the page and are treated by some file systems
    82  // as the same path. If we do allow Unicode marks in paths, there
    83  // must be some kind of normalization to allow only one canonical
    84  // encoding of any character used in an import path.
    85  package module
    86  
    87  // IMPORTANT NOTE
    88  //
    89  // This file essentially defines the set of valid import paths for the go command.
    90  // There are many subtle considerations, including Unicode ambiguity,
    91  // security, network, and file system representations.
    92  //
    93  // This file also defines the set of valid module path and version combinations,
    94  // another topic with many subtle considerations.
    95  //
    96  // Changes to the semantics in this file require approval from rsc.
    97  
    98  import (
    99  	"errors"
   100  	"fmt"
   101  	"path"
   102  	"sort"
   103  	"strings"
   104  	"unicode"
   105  	"unicode/utf8"
   106  
   107  	"golang.org/x/mod/semver"
   108  )
   109  
   110  // A Version (for clients, a module.Version) is defined by a module path and version pair.
   111  // These are stored in their plain (unescaped) form.
   112  type Version struct {
   113  	// Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
   114  	Path string
   115  
   116  	// Version is usually a semantic version in canonical form.
   117  	// There are three exceptions to this general rule.
   118  	// First, the top-level target of a build has no specific version
   119  	// and uses Version = "".
   120  	// Second, during MVS calculations the version "none" is used
   121  	// to represent the decision to take no version of a given module.
   122  	// Third, filesystem paths found in "replace" directives are
   123  	// represented by a path with an empty version.
   124  	Version string `json:",omitempty"`
   125  }
   126  
   127  // String returns a representation of the Version suitable for logging
   128  // (Path@Version, or just Path if Version is empty).
   129  func (m Version) String() string {
   130  	if m.Version == "" {
   131  		return m.Path
   132  	}
   133  	return m.Path + "@" + m.Version
   134  }
   135  
   136  // A ModuleError indicates an error specific to a module.
   137  type ModuleError struct {
   138  	Path    string
   139  	Version string
   140  	Err     error
   141  }
   142  
   143  // VersionError returns a [ModuleError] derived from a [Version] and error,
   144  // or err itself if it is already such an error.
   145  func VersionError(v Version, err error) error {
   146  	var mErr *ModuleError
   147  	if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version {
   148  		return err
   149  	}
   150  	return &ModuleError{
   151  		Path:    v.Path,
   152  		Version: v.Version,
   153  		Err:     err,
   154  	}
   155  }
   156  
   157  func (e *ModuleError) Error() string {
   158  	if v, ok := e.Err.(*InvalidVersionError); ok {
   159  		return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
   160  	}
   161  	if e.Version != "" {
   162  		return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
   163  	}
   164  	return fmt.Sprintf("module %s: %v", e.Path, e.Err)
   165  }
   166  
   167  func (e *ModuleError) Unwrap() error { return e.Err }
   168  
   169  // An InvalidVersionError indicates an error specific to a version, with the
   170  // module path unknown or specified externally.
   171  //
   172  // A [ModuleError] may wrap an InvalidVersionError, but an InvalidVersionError
   173  // must not wrap a ModuleError.
   174  type InvalidVersionError struct {
   175  	Version string
   176  	Pseudo  bool
   177  	Err     error
   178  }
   179  
   180  // noun returns either "version" or "pseudo-version", depending on whether
   181  // e.Version is a pseudo-version.
   182  func (e *InvalidVersionError) noun() string {
   183  	if e.Pseudo {
   184  		return "pseudo-version"
   185  	}
   186  	return "version"
   187  }
   188  
   189  func (e *InvalidVersionError) Error() string {
   190  	return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
   191  }
   192  
   193  func (e *InvalidVersionError) Unwrap() error { return e.Err }
   194  
   195  // An InvalidPathError indicates a module, import, or file path doesn't
   196  // satisfy all naming constraints. See [CheckPath], [CheckImportPath],
   197  // and [CheckFilePath] for specific restrictions.
   198  type InvalidPathError struct {
   199  	Kind string // "module", "import", or "file"
   200  	Path string
   201  	Err  error
   202  }
   203  
   204  func (e *InvalidPathError) Error() string {
   205  	return fmt.Sprintf("malformed %s path %q: %v", e.Kind, e.Path, e.Err)
   206  }
   207  
   208  func (e *InvalidPathError) Unwrap() error { return e.Err }
   209  
   210  // Check checks that a given module path, version pair is valid.
   211  // In addition to the path being a valid module path
   212  // and the version being a valid semantic version,
   213  // the two must correspond.
   214  // For example, the path "yaml/v2" only corresponds to
   215  // semantic versions beginning with "v2.".
   216  func Check(path, version string) error {
   217  	if err := CheckPath(path); err != nil {
   218  		return err
   219  	}
   220  	if !semver.IsValid(version) {
   221  		return &ModuleError{
   222  			Path: path,
   223  			Err:  &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
   224  		}
   225  	}
   226  	_, pathMajor, _ := SplitPathVersion(path)
   227  	if err := CheckPathMajor(version, pathMajor); err != nil {
   228  		return &ModuleError{Path: path, Err: err}
   229  	}
   230  	return nil
   231  }
   232  
   233  // firstPathOK reports whether r can appear in the first element of a module path.
   234  // The first element of the path must be an LDH domain name, at least for now.
   235  // To avoid case ambiguity, the domain name must be entirely lower case.
   236  func firstPathOK(r rune) bool {
   237  	return r == '-' || r == '.' ||
   238  		'0' <= r && r <= '9' ||
   239  		'a' <= r && r <= 'z'
   240  }
   241  
   242  // modPathOK reports whether r can appear in a module path element.
   243  // Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
   244  //
   245  // This matches what "go get" has historically recognized in import paths,
   246  // and avoids confusing sequences like '%20' or '+' that would change meaning
   247  // if used in a URL.
   248  //
   249  // TODO(rsc): We would like to allow Unicode letters, but that requires additional
   250  // care in the safe encoding (see "escaped paths" above).
   251  func modPathOK(r rune) bool {
   252  	if r < utf8.RuneSelf {
   253  		return r == '-' || r == '.' || r == '_' || r == '~' ||
   254  			'0' <= r && r <= '9' ||
   255  			'A' <= r && r <= 'Z' ||
   256  			'a' <= r && r <= 'z'
   257  	}
   258  	return false
   259  }
   260  
   261  // importPathOK reports whether r can appear in a package import path element.
   262  //
   263  // Import paths are intermediate between module paths and file paths: we allow
   264  // disallow characters that would be confusing or ambiguous as arguments to
   265  // 'go get' (such as '@' and ' ' ), but allow certain characters that are
   266  // otherwise-unambiguous on the command line and historically used for some
   267  // binary names (such as '++' as a suffix for compiler binaries and wrappers).
   268  func importPathOK(r rune) bool {
   269  	return modPathOK(r) || r == '+'
   270  }
   271  
   272  // fileNameOK reports whether r can appear in a file name.
   273  // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
   274  // If we expand the set of allowed characters here, we have to
   275  // work harder at detecting potential case-folding and normalization collisions.
   276  // See note about "escaped paths" above.
   277  func fileNameOK(r rune) bool {
   278  	if r < utf8.RuneSelf {
   279  		// Entire set of ASCII punctuation, from which we remove characters:
   280  		//     ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
   281  		// We disallow some shell special characters: " ' * < > ? ` |
   282  		// (Note that some of those are disallowed by the Windows file system as well.)
   283  		// We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
   284  		// We allow spaces (U+0020) in file names.
   285  		const allowed = "!#$%&()+,-.=@[]^_{}~ "
   286  		if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
   287  			return true
   288  		}
   289  		return strings.ContainsRune(allowed, r)
   290  	}
   291  	// It may be OK to add more ASCII punctuation here, but only carefully.
   292  	// For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
   293  	return unicode.IsLetter(r)
   294  }
   295  
   296  // CheckPath checks that a module path is valid.
   297  // A valid module path is a valid import path, as checked by [CheckImportPath],
   298  // with three additional constraints.
   299  // First, the leading path element (up to the first slash, if any),
   300  // by convention a domain name, must contain only lower-case ASCII letters,
   301  // ASCII digits, dots (U+002E), and dashes (U+002D);
   302  // it must contain at least one dot and cannot start with a dash.
   303  // Second, for a final path element of the form /vN, where N looks numeric
   304  // (ASCII digits and dots) must not begin with a leading zero, must not be /v1,
   305  // and must not contain any dots. For paths beginning with "gopkg.in/",
   306  // this second requirement is replaced by a requirement that the path
   307  // follow the gopkg.in server's conventions.
   308  // Third, no path element may begin with a dot.
   309  func CheckPath(path string) (err error) {
   310  	defer func() {
   311  		if err != nil {
   312  			err = &InvalidPathError{Kind: "module", Path: path, Err: err}
   313  		}
   314  	}()
   315  
   316  	if err := checkPath(path, modulePath); err != nil {
   317  		return err
   318  	}
   319  	i := strings.Index(path, "/")
   320  	if i < 0 {
   321  		i = len(path)
   322  	}
   323  	if i == 0 {
   324  		return fmt.Errorf("leading slash")
   325  	}
   326  	if !strings.Contains(path[:i], ".") {
   327  		return fmt.Errorf("missing dot in first path element")
   328  	}
   329  	if path[0] == '-' {
   330  		return fmt.Errorf("leading dash in first path element")
   331  	}
   332  	for _, r := range path[:i] {
   333  		if !firstPathOK(r) {
   334  			return fmt.Errorf("invalid char %q in first path element", r)
   335  		}
   336  	}
   337  	if _, _, ok := SplitPathVersion(path); !ok {
   338  		return fmt.Errorf("invalid version")
   339  	}
   340  	return nil
   341  }
   342  
   343  // CheckImportPath checks that an import path is valid.
   344  //
   345  // A valid import path consists of one or more valid path elements
   346  // separated by slashes (U+002F). (It must not begin with nor end in a slash.)
   347  //
   348  // A valid path element is a non-empty string made up of
   349  // ASCII letters, ASCII digits, and limited ASCII punctuation: - . _ and ~.
   350  // It must not end with a dot (U+002E), nor contain two dots in a row.
   351  //
   352  // The element prefix up to the first dot must not be a reserved file name
   353  // on Windows, regardless of case (CON, com1, NuL, and so on). The element
   354  // must not have a suffix of a tilde followed by one or more ASCII digits
   355  // (to exclude paths elements that look like Windows short-names).
   356  //
   357  // CheckImportPath may be less restrictive in the future, but see the
   358  // top-level package documentation for additional information about
   359  // subtleties of Unicode.
   360  func CheckImportPath(path string) error {
   361  	if err := checkPath(path, importPath); err != nil {
   362  		return &InvalidPathError{Kind: "import", Path: path, Err: err}
   363  	}
   364  	return nil
   365  }
   366  
   367  // pathKind indicates what kind of path we're checking. Module paths,
   368  // import paths, and file paths have different restrictions.
   369  type pathKind int
   370  
   371  const (
   372  	modulePath pathKind = iota
   373  	importPath
   374  	filePath
   375  )
   376  
   377  // checkPath checks that a general path is valid. kind indicates what
   378  // specific constraints should be applied.
   379  //
   380  // checkPath returns an error describing why the path is not valid.
   381  // Because these checks apply to module, import, and file paths,
   382  // and because other checks may be applied, the caller is expected to wrap
   383  // this error with [InvalidPathError].
   384  func checkPath(path string, kind pathKind) error {
   385  	if !utf8.ValidString(path) {
   386  		return fmt.Errorf("invalid UTF-8")
   387  	}
   388  	if path == "" {
   389  		return fmt.Errorf("empty string")
   390  	}
   391  	if path[0] == '-' && kind != filePath {
   392  		return fmt.Errorf("leading dash")
   393  	}
   394  	if strings.Contains(path, "//") {
   395  		return fmt.Errorf("double slash")
   396  	}
   397  	if path[len(path)-1] == '/' {
   398  		return fmt.Errorf("trailing slash")
   399  	}
   400  	elemStart := 0
   401  	for i, r := range path {
   402  		if r == '/' {
   403  			if err := checkElem(path[elemStart:i], kind); err != nil {
   404  				return err
   405  			}
   406  			elemStart = i + 1
   407  		}
   408  	}
   409  	if err := checkElem(path[elemStart:], kind); err != nil {
   410  		return err
   411  	}
   412  	return nil
   413  }
   414  
   415  // checkElem checks whether an individual path element is valid.
   416  func checkElem(elem string, kind pathKind) error {
   417  	if elem == "" {
   418  		return fmt.Errorf("empty path element")
   419  	}
   420  	if strings.Count(elem, ".") == len(elem) {
   421  		return fmt.Errorf("invalid path element %q", elem)
   422  	}
   423  	if elem[0] == '.' && kind == modulePath {
   424  		return fmt.Errorf("leading dot in path element")
   425  	}
   426  	if elem[len(elem)-1] == '.' {
   427  		return fmt.Errorf("trailing dot in path element")
   428  	}
   429  	for _, r := range elem {
   430  		ok := false
   431  		switch kind {
   432  		case modulePath:
   433  			ok = modPathOK(r)
   434  		case importPath:
   435  			ok = importPathOK(r)
   436  		case filePath:
   437  			ok = fileNameOK(r)
   438  		default:
   439  			panic(fmt.Sprintf("internal error: invalid kind %v", kind))
   440  		}
   441  		if !ok {
   442  			return fmt.Errorf("invalid char %q", r)
   443  		}
   444  	}
   445  
   446  	// Windows disallows a bunch of path elements, sadly.
   447  	// See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
   448  	short := elem
   449  	if i := strings.Index(short, "."); i >= 0 {
   450  		short = short[:i]
   451  	}
   452  	for _, bad := range badWindowsNames {
   453  		if strings.EqualFold(bad, short) {
   454  			return fmt.Errorf("%q disallowed as path element component on Windows", short)
   455  		}
   456  	}
   457  
   458  	if kind == filePath {
   459  		// don't check for Windows short-names in file names. They're
   460  		// only an issue for import paths.
   461  		return nil
   462  	}
   463  
   464  	// Reject path components that look like Windows short-names.
   465  	// Those usually end in a tilde followed by one or more ASCII digits.
   466  	if tilde := strings.LastIndexByte(short, '~'); tilde >= 0 && tilde < len(short)-1 {
   467  		suffix := short[tilde+1:]
   468  		suffixIsDigits := true
   469  		for _, r := range suffix {
   470  			if r < '0' || r > '9' {
   471  				suffixIsDigits = false
   472  				break
   473  			}
   474  		}
   475  		if suffixIsDigits {
   476  			return fmt.Errorf("trailing tilde and digits in path element")
   477  		}
   478  	}
   479  
   480  	return nil
   481  }
   482  
   483  // CheckFilePath checks that a slash-separated file path is valid.
   484  // The definition of a valid file path is the same as the definition
   485  // of a valid import path except that the set of allowed characters is larger:
   486  // all Unicode letters, ASCII digits, the ASCII space character (U+0020),
   487  // and the ASCII punctuation characters
   488  // “!#$%&()+,-.=@[]^_{}~”.
   489  // (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
   490  // have special meanings in certain shells or operating systems.)
   491  //
   492  // CheckFilePath may be less restrictive in the future, but see the
   493  // top-level package documentation for additional information about
   494  // subtleties of Unicode.
   495  func CheckFilePath(path string) error {
   496  	if err := checkPath(path, filePath); err != nil {
   497  		return &InvalidPathError{Kind: "file", Path: path, Err: err}
   498  	}
   499  	return nil
   500  }
   501  
   502  // badWindowsNames are the reserved file path elements on Windows.
   503  // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
   504  var badWindowsNames = []string{
   505  	"CON",
   506  	"PRN",
   507  	"AUX",
   508  	"NUL",
   509  	"COM1",
   510  	"COM2",
   511  	"COM3",
   512  	"COM4",
   513  	"COM5",
   514  	"COM6",
   515  	"COM7",
   516  	"COM8",
   517  	"COM9",
   518  	"LPT1",
   519  	"LPT2",
   520  	"LPT3",
   521  	"LPT4",
   522  	"LPT5",
   523  	"LPT6",
   524  	"LPT7",
   525  	"LPT8",
   526  	"LPT9",
   527  }
   528  
   529  // SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
   530  // and version is either empty or "/vN" for N >= 2.
   531  // As a special case, gopkg.in paths are recognized directly;
   532  // they require ".vN" instead of "/vN", and for all N, not just N >= 2.
   533  // SplitPathVersion returns with ok = false when presented with
   534  // a path whose last path element does not satisfy the constraints
   535  // applied by [CheckPath], such as "example.com/pkg/v1" or "example.com/pkg/v1.2".
   536  func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
   537  	if strings.HasPrefix(path, "gopkg.in/") {
   538  		return splitGopkgIn(path)
   539  	}
   540  
   541  	i := len(path)
   542  	dot := false
   543  	for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
   544  		if path[i-1] == '.' {
   545  			dot = true
   546  		}
   547  		i--
   548  	}
   549  	if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
   550  		return path, "", true
   551  	}
   552  	prefix, pathMajor = path[:i-2], path[i-2:]
   553  	if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
   554  		return path, "", false
   555  	}
   556  	return prefix, pathMajor, true
   557  }
   558  
   559  // splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
   560  func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
   561  	if !strings.HasPrefix(path, "gopkg.in/") {
   562  		return path, "", false
   563  	}
   564  	i := len(path)
   565  	if strings.HasSuffix(path, "-unstable") {
   566  		i -= len("-unstable")
   567  	}
   568  	for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
   569  		i--
   570  	}
   571  	if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
   572  		// All gopkg.in paths must end in vN for some N.
   573  		return path, "", false
   574  	}
   575  	prefix, pathMajor = path[:i-2], path[i-2:]
   576  	if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
   577  		return path, "", false
   578  	}
   579  	return prefix, pathMajor, true
   580  }
   581  
   582  // MatchPathMajor reports whether the semantic version v
   583  // matches the path major version pathMajor.
   584  //
   585  // MatchPathMajor returns true if and only if [CheckPathMajor] returns nil.
   586  func MatchPathMajor(v, pathMajor string) bool {
   587  	return CheckPathMajor(v, pathMajor) == nil
   588  }
   589  
   590  // CheckPathMajor returns a non-nil error if the semantic version v
   591  // does not match the path major version pathMajor.
   592  func CheckPathMajor(v, pathMajor string) error {
   593  	// TODO(jayconrod): return errors or panic for invalid inputs. This function
   594  	// (and others) was covered by integration tests for cmd/go, and surrounding
   595  	// code protected against invalid inputs like non-canonical versions.
   596  	if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
   597  		pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
   598  	}
   599  	if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
   600  		// Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
   601  		// For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
   602  		return nil
   603  	}
   604  	m := semver.Major(v)
   605  	if pathMajor == "" {
   606  		if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
   607  			return nil
   608  		}
   609  		pathMajor = "v0 or v1"
   610  	} else if pathMajor[0] == '/' || pathMajor[0] == '.' {
   611  		if m == pathMajor[1:] {
   612  			return nil
   613  		}
   614  		pathMajor = pathMajor[1:]
   615  	}
   616  	return &InvalidVersionError{
   617  		Version: v,
   618  		Err:     fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
   619  	}
   620  }
   621  
   622  // PathMajorPrefix returns the major-version tag prefix implied by pathMajor.
   623  // An empty PathMajorPrefix allows either v0 or v1.
   624  //
   625  // Note that [MatchPathMajor] may accept some versions that do not actually begin
   626  // with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1'
   627  // pathMajor, even though that pathMajor implies 'v1' tagging.
   628  func PathMajorPrefix(pathMajor string) string {
   629  	if pathMajor == "" {
   630  		return ""
   631  	}
   632  	if pathMajor[0] != '/' && pathMajor[0] != '.' {
   633  		panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
   634  	}
   635  	if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
   636  		pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
   637  	}
   638  	m := pathMajor[1:]
   639  	if m != semver.Major(m) {
   640  		panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
   641  	}
   642  	return m
   643  }
   644  
   645  // CanonicalVersion returns the canonical form of the version string v.
   646  // It is the same as [semver.Canonical] except that it preserves the special build suffix "+incompatible".
   647  func CanonicalVersion(v string) string {
   648  	cv := semver.Canonical(v)
   649  	if semver.Build(v) == "+incompatible" {
   650  		cv += "+incompatible"
   651  	}
   652  	return cv
   653  }
   654  
   655  // Sort sorts the list by Path, breaking ties by comparing [Version] fields.
   656  // The Version fields are interpreted as semantic versions (using [semver.Compare])
   657  // optionally followed by a tie-breaking suffix introduced by a slash character,
   658  // like in "v0.0.1/go.mod".
   659  func Sort(list []Version) {
   660  	sort.Slice(list, func(i, j int) bool {
   661  		mi := list[i]
   662  		mj := list[j]
   663  		if mi.Path != mj.Path {
   664  			return mi.Path < mj.Path
   665  		}
   666  		// To help go.sum formatting, allow version/file.
   667  		// Compare semver prefix by semver rules,
   668  		// file by string order.
   669  		vi := mi.Version
   670  		vj := mj.Version
   671  		var fi, fj string
   672  		if k := strings.Index(vi, "/"); k >= 0 {
   673  			vi, fi = vi[:k], vi[k:]
   674  		}
   675  		if k := strings.Index(vj, "/"); k >= 0 {
   676  			vj, fj = vj[:k], vj[k:]
   677  		}
   678  		if vi != vj {
   679  			return semver.Compare(vi, vj) < 0
   680  		}
   681  		return fi < fj
   682  	})
   683  }
   684  
   685  // EscapePath returns the escaped form of the given module path.
   686  // It fails if the module path is invalid.
   687  func EscapePath(path string) (escaped string, err error) {
   688  	if err := CheckPath(path); err != nil {
   689  		return "", err
   690  	}
   691  
   692  	return escapeString(path)
   693  }
   694  
   695  // EscapeVersion returns the escaped form of the given module version.
   696  // Versions are allowed to be in non-semver form but must be valid file names
   697  // and not contain exclamation marks.
   698  func EscapeVersion(v string) (escaped string, err error) {
   699  	if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
   700  		return "", &InvalidVersionError{
   701  			Version: v,
   702  			Err:     fmt.Errorf("disallowed version string"),
   703  		}
   704  	}
   705  	return escapeString(v)
   706  }
   707  
   708  func escapeString(s string) (escaped string, err error) {
   709  	haveUpper := false
   710  	for _, r := range s {
   711  		if r == '!' || r >= utf8.RuneSelf {
   712  			// This should be disallowed by CheckPath, but diagnose anyway.
   713  			// The correctness of the escaping loop below depends on it.
   714  			return "", fmt.Errorf("internal error: inconsistency in EscapePath")
   715  		}
   716  		if 'A' <= r && r <= 'Z' {
   717  			haveUpper = true
   718  		}
   719  	}
   720  
   721  	if !haveUpper {
   722  		return s, nil
   723  	}
   724  
   725  	var buf []byte
   726  	for _, r := range s {
   727  		if 'A' <= r && r <= 'Z' {
   728  			buf = append(buf, '!', byte(r+'a'-'A'))
   729  		} else {
   730  			buf = append(buf, byte(r))
   731  		}
   732  	}
   733  	return string(buf), nil
   734  }
   735  
   736  // UnescapePath returns the module path for the given escaped path.
   737  // It fails if the escaped path is invalid or describes an invalid path.
   738  func UnescapePath(escaped string) (path string, err error) {
   739  	path, ok := unescapeString(escaped)
   740  	if !ok {
   741  		return "", fmt.Errorf("invalid escaped module path %q", escaped)
   742  	}
   743  	if err := CheckPath(path); err != nil {
   744  		return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err)
   745  	}
   746  	return path, nil
   747  }
   748  
   749  // UnescapeVersion returns the version string for the given escaped version.
   750  // It fails if the escaped form is invalid or describes an invalid version.
   751  // Versions are allowed to be in non-semver form but must be valid file names
   752  // and not contain exclamation marks.
   753  func UnescapeVersion(escaped string) (v string, err error) {
   754  	v, ok := unescapeString(escaped)
   755  	if !ok {
   756  		return "", fmt.Errorf("invalid escaped version %q", escaped)
   757  	}
   758  	if err := checkElem(v, filePath); err != nil {
   759  		return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
   760  	}
   761  	return v, nil
   762  }
   763  
   764  func unescapeString(escaped string) (string, bool) {
   765  	var buf []byte
   766  
   767  	bang := false
   768  	for _, r := range escaped {
   769  		if r >= utf8.RuneSelf {
   770  			return "", false
   771  		}
   772  		if bang {
   773  			bang = false
   774  			if r < 'a' || 'z' < r {
   775  				return "", false
   776  			}
   777  			buf = append(buf, byte(r+'A'-'a'))
   778  			continue
   779  		}
   780  		if r == '!' {
   781  			bang = true
   782  			continue
   783  		}
   784  		if 'A' <= r && r <= 'Z' {
   785  			return "", false
   786  		}
   787  		buf = append(buf, byte(r))
   788  	}
   789  	if bang {
   790  		return "", false
   791  	}
   792  	return string(buf), true
   793  }
   794  
   795  // MatchPrefixPatterns reports whether any path prefix of target matches one of
   796  // the glob patterns (as defined by [path.Match]) in the comma-separated globs
   797  // list. This implements the algorithm used when matching a module path to the
   798  // GOPRIVATE environment variable, as described by 'go help module-private'.
   799  //
   800  // It ignores any empty or malformed patterns in the list.
   801  // Trailing slashes on patterns are ignored.
   802  func MatchPrefixPatterns(globs, target string) bool {
   803  	for globs != "" {
   804  		// Extract next non-empty glob in comma-separated list.
   805  		var glob string
   806  		if i := strings.Index(globs, ","); i >= 0 {
   807  			glob, globs = globs[:i], globs[i+1:]
   808  		} else {
   809  			glob, globs = globs, ""
   810  		}
   811  		glob = strings.TrimSuffix(glob, "/")
   812  		if glob == "" {
   813  			continue
   814  		}
   815  
   816  		// A glob with N+1 path elements (N slashes) needs to be matched
   817  		// against the first N+1 path elements of target,
   818  		// which end just before the N+1'th slash.
   819  		n := strings.Count(glob, "/")
   820  		prefix := target
   821  		// Walk target, counting slashes, truncating at the N+1'th slash.
   822  		for i := 0; i < len(target); i++ {
   823  			if target[i] == '/' {
   824  				if n == 0 {
   825  					prefix = target[:i]
   826  					break
   827  				}
   828  				n--
   829  			}
   830  		}
   831  		if n > 0 {
   832  			// Not enough prefix elements.
   833  			continue
   834  		}
   835  		matched, _ := path.Match(glob, prefix)
   836  		if matched {
   837  			return true
   838  		}
   839  	}
   840  	return false
   841  }
   842  

View as plain text