Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / mod@v0.3.0 / module / module.go
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         "fmt"
100         "sort"
101         "strings"
102         "unicode"
103         "unicode/utf8"
104
105         "golang.org/x/mod/semver"
106         errors "golang.org/x/xerrors"
107 )
108
109 // A Version (for clients, a module.Version) is defined by a module path and version pair.
110 // These are stored in their plain (unescaped) form.
111 type Version struct {
112         // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
113         Path string
114
115         // Version is usually a semantic version in canonical form.
116         // There are three exceptions to this general rule.
117         // First, the top-level target of a build has no specific version
118         // and uses Version = "".
119         // Second, during MVS calculations the version "none" is used
120         // to represent the decision to take no version of a given module.
121         // Third, filesystem paths found in "replace" directives are
122         // represented by a path with an empty version.
123         Version string `json:",omitempty"`
124 }
125
126 // String returns a representation of the Version suitable for logging
127 // (Path@Version, or just Path if Version is empty).
128 func (m Version) String() string {
129         if m.Version == "" {
130                 return m.Path
131         }
132         return m.Path + "@" + m.Version
133 }
134
135 // A ModuleError indicates an error specific to a module.
136 type ModuleError struct {
137         Path    string
138         Version string
139         Err     error
140 }
141
142 // VersionError returns a ModuleError derived from a Version and error,
143 // or err itself if it is already such an error.
144 func VersionError(v Version, err error) error {
145         var mErr *ModuleError
146         if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version {
147                 return err
148         }
149         return &ModuleError{
150                 Path:    v.Path,
151                 Version: v.Version,
152                 Err:     err,
153         }
154 }
155
156 func (e *ModuleError) Error() string {
157         if v, ok := e.Err.(*InvalidVersionError); ok {
158                 return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
159         }
160         if e.Version != "" {
161                 return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
162         }
163         return fmt.Sprintf("module %s: %v", e.Path, e.Err)
164 }
165
166 func (e *ModuleError) Unwrap() error { return e.Err }
167
168 // An InvalidVersionError indicates an error specific to a version, with the
169 // module path unknown or specified externally.
170 //
171 // A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError
172 // must not wrap a ModuleError.
173 type InvalidVersionError struct {
174         Version string
175         Pseudo  bool
176         Err     error
177 }
178
179 // noun returns either "version" or "pseudo-version", depending on whether
180 // e.Version is a pseudo-version.
181 func (e *InvalidVersionError) noun() string {
182         if e.Pseudo {
183                 return "pseudo-version"
184         }
185         return "version"
186 }
187
188 func (e *InvalidVersionError) Error() string {
189         return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
190 }
191
192 func (e *InvalidVersionError) Unwrap() error { return e.Err }
193
194 // Check checks that a given module path, version pair is valid.
195 // In addition to the path being a valid module path
196 // and the version being a valid semantic version,
197 // the two must correspond.
198 // For example, the path "yaml/v2" only corresponds to
199 // semantic versions beginning with "v2.".
200 func Check(path, version string) error {
201         if err := CheckPath(path); err != nil {
202                 return err
203         }
204         if !semver.IsValid(version) {
205                 return &ModuleError{
206                         Path: path,
207                         Err:  &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
208                 }
209         }
210         _, pathMajor, _ := SplitPathVersion(path)
211         if err := CheckPathMajor(version, pathMajor); err != nil {
212                 return &ModuleError{Path: path, Err: err}
213         }
214         return nil
215 }
216
217 // firstPathOK reports whether r can appear in the first element of a module path.
218 // The first element of the path must be an LDH domain name, at least for now.
219 // To avoid case ambiguity, the domain name must be entirely lower case.
220 func firstPathOK(r rune) bool {
221         return r == '-' || r == '.' ||
222                 '0' <= r && r <= '9' ||
223                 'a' <= r && r <= 'z'
224 }
225
226 // pathOK reports whether r can appear in an import path element.
227 // Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
228 // This matches what "go get" has historically recognized in import paths.
229 // TODO(rsc): We would like to allow Unicode letters, but that requires additional
230 // care in the safe encoding (see "escaped paths" above).
231 func pathOK(r rune) bool {
232         if r < utf8.RuneSelf {
233                 return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
234                         '0' <= r && r <= '9' ||
235                         'A' <= r && r <= 'Z' ||
236                         'a' <= r && r <= 'z'
237         }
238         return false
239 }
240
241 // fileNameOK reports whether r can appear in a file name.
242 // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters.
243 // If we expand the set of allowed characters here, we have to
244 // work harder at detecting potential case-folding and normalization collisions.
245 // See note about "escaped paths" above.
246 func fileNameOK(r rune) bool {
247         if r < utf8.RuneSelf {
248                 // Entire set of ASCII punctuation, from which we remove characters:
249                 //     ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
250                 // We disallow some shell special characters: " ' * < > ? ` |
251                 // (Note that some of those are disallowed by the Windows file system as well.)
252                 // We also disallow path separators / : and \ (fileNameOK is only called on path element characters).
253                 // We allow spaces (U+0020) in file names.
254                 const allowed = "!#$%&()+,-.=@[]^_{}~ "
255                 if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
256                         return true
257                 }
258                 for i := 0; i < len(allowed); i++ {
259                         if rune(allowed[i]) == r {
260                                 return true
261                         }
262                 }
263                 return false
264         }
265         // It may be OK to add more ASCII punctuation here, but only carefully.
266         // For example Windows disallows < > \, and macOS disallows :, so we must not allow those.
267         return unicode.IsLetter(r)
268 }
269
270 // CheckPath checks that a module path is valid.
271 // A valid module path is a valid import path, as checked by CheckImportPath,
272 // with two additional constraints.
273 // First, the leading path element (up to the first slash, if any),
274 // by convention a domain name, must contain only lower-case ASCII letters,
275 // ASCII digits, dots (U+002E), and dashes (U+002D);
276 // it must contain at least one dot and cannot start with a dash.
277 // Second, for a final path element of the form /vN, where N looks numeric
278 // (ASCII digits and dots) must not begin with a leading zero, must not be /v1,
279 // and must not contain any dots. For paths beginning with "gopkg.in/",
280 // this second requirement is replaced by a requirement that the path
281 // follow the gopkg.in server's conventions.
282 func CheckPath(path string) error {
283         if err := checkPath(path, false); err != nil {
284                 return fmt.Errorf("malformed module path %q: %v", path, err)
285         }
286         i := strings.Index(path, "/")
287         if i < 0 {
288                 i = len(path)
289         }
290         if i == 0 {
291                 return fmt.Errorf("malformed module path %q: leading slash", path)
292         }
293         if !strings.Contains(path[:i], ".") {
294                 return fmt.Errorf("malformed module path %q: missing dot in first path element", path)
295         }
296         if path[0] == '-' {
297                 return fmt.Errorf("malformed module path %q: leading dash in first path element", path)
298         }
299         for _, r := range path[:i] {
300                 if !firstPathOK(r) {
301                         return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r)
302                 }
303         }
304         if _, _, ok := SplitPathVersion(path); !ok {
305                 return fmt.Errorf("malformed module path %q: invalid version", path)
306         }
307         return nil
308 }
309
310 // CheckImportPath checks that an import path is valid.
311 //
312 // A valid import path consists of one or more valid path elements
313 // separated by slashes (U+002F). (It must not begin with nor end in a slash.)
314 //
315 // A valid path element is a non-empty string made up of
316 // ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~.
317 // It must not begin or end with a dot (U+002E), nor contain two dots in a row.
318 //
319 // The element prefix up to the first dot must not be a reserved file name
320 // on Windows, regardless of case (CON, com1, NuL, and so on).
321 //
322 // CheckImportPath may be less restrictive in the future, but see the
323 // top-level package documentation for additional information about
324 // subtleties of Unicode.
325 func CheckImportPath(path string) error {
326         if err := checkPath(path, false); err != nil {
327                 return fmt.Errorf("malformed import path %q: %v", path, err)
328         }
329         return nil
330 }
331
332 // checkPath checks that a general path is valid.
333 // It returns an error describing why but not mentioning path.
334 // Because these checks apply to both module paths and import paths,
335 // the caller is expected to add the "malformed ___ path %q: " prefix.
336 // fileName indicates whether the final element of the path is a file name
337 // (as opposed to a directory name).
338 func checkPath(path string, fileName bool) error {
339         if !utf8.ValidString(path) {
340                 return fmt.Errorf("invalid UTF-8")
341         }
342         if path == "" {
343                 return fmt.Errorf("empty string")
344         }
345         if path[0] == '-' {
346                 return fmt.Errorf("leading dash")
347         }
348         if strings.Contains(path, "//") {
349                 return fmt.Errorf("double slash")
350         }
351         if path[len(path)-1] == '/' {
352                 return fmt.Errorf("trailing slash")
353         }
354         elemStart := 0
355         for i, r := range path {
356                 if r == '/' {
357                         if err := checkElem(path[elemStart:i], fileName); err != nil {
358                                 return err
359                         }
360                         elemStart = i + 1
361                 }
362         }
363         if err := checkElem(path[elemStart:], fileName); err != nil {
364                 return err
365         }
366         return nil
367 }
368
369 // checkElem checks whether an individual path element is valid.
370 // fileName indicates whether the element is a file name (not a directory name).
371 func checkElem(elem string, fileName bool) error {
372         if elem == "" {
373                 return fmt.Errorf("empty path element")
374         }
375         if strings.Count(elem, ".") == len(elem) {
376                 return fmt.Errorf("invalid path element %q", elem)
377         }
378         if elem[0] == '.' && !fileName {
379                 return fmt.Errorf("leading dot in path element")
380         }
381         if elem[len(elem)-1] == '.' {
382                 return fmt.Errorf("trailing dot in path element")
383         }
384         charOK := pathOK
385         if fileName {
386                 charOK = fileNameOK
387         }
388         for _, r := range elem {
389                 if !charOK(r) {
390                         return fmt.Errorf("invalid char %q", r)
391                 }
392         }
393
394         // Windows disallows a bunch of path elements, sadly.
395         // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
396         short := elem
397         if i := strings.Index(short, "."); i >= 0 {
398                 short = short[:i]
399         }
400         for _, bad := range badWindowsNames {
401                 if strings.EqualFold(bad, short) {
402                         return fmt.Errorf("%q disallowed as path element component on Windows", short)
403                 }
404         }
405         return nil
406 }
407
408 // CheckFilePath checks that a slash-separated file path is valid.
409 // The definition of a valid file path is the same as the definition
410 // of a valid import path except that the set of allowed characters is larger:
411 // all Unicode letters, ASCII digits, the ASCII space character (U+0020),
412 // and the ASCII punctuation characters
413 // “!#$%&()+,-.=@[]^_{}~”.
414 // (The excluded punctuation characters, " * < > ? ` ' | / \ and :,
415 // have special meanings in certain shells or operating systems.)
416 //
417 // CheckFilePath may be less restrictive in the future, but see the
418 // top-level package documentation for additional information about
419 // subtleties of Unicode.
420 func CheckFilePath(path string) error {
421         if err := checkPath(path, true); err != nil {
422                 return fmt.Errorf("malformed file path %q: %v", path, err)
423         }
424         return nil
425 }
426
427 // badWindowsNames are the reserved file path elements on Windows.
428 // See https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
429 var badWindowsNames = []string{
430         "CON",
431         "PRN",
432         "AUX",
433         "NUL",
434         "COM1",
435         "COM2",
436         "COM3",
437         "COM4",
438         "COM5",
439         "COM6",
440         "COM7",
441         "COM8",
442         "COM9",
443         "LPT1",
444         "LPT2",
445         "LPT3",
446         "LPT4",
447         "LPT5",
448         "LPT6",
449         "LPT7",
450         "LPT8",
451         "LPT9",
452 }
453
454 // SplitPathVersion returns prefix and major version such that prefix+pathMajor == path
455 // and version is either empty or "/vN" for N >= 2.
456 // As a special case, gopkg.in paths are recognized directly;
457 // they require ".vN" instead of "/vN", and for all N, not just N >= 2.
458 // SplitPathVersion returns with ok = false when presented with
459 // a path whose last path element does not satisfy the constraints
460 // applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2".
461 func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
462         if strings.HasPrefix(path, "gopkg.in/") {
463                 return splitGopkgIn(path)
464         }
465
466         i := len(path)
467         dot := false
468         for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
469                 if path[i-1] == '.' {
470                         dot = true
471                 }
472                 i--
473         }
474         if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
475                 return path, "", true
476         }
477         prefix, pathMajor = path[:i-2], path[i-2:]
478         if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
479                 return path, "", false
480         }
481         return prefix, pathMajor, true
482 }
483
484 // splitGopkgIn is like SplitPathVersion but only for gopkg.in paths.
485 func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
486         if !strings.HasPrefix(path, "gopkg.in/") {
487                 return path, "", false
488         }
489         i := len(path)
490         if strings.HasSuffix(path, "-unstable") {
491                 i -= len("-unstable")
492         }
493         for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
494                 i--
495         }
496         if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
497                 // All gopkg.in paths must end in vN for some N.
498                 return path, "", false
499         }
500         prefix, pathMajor = path[:i-2], path[i-2:]
501         if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
502                 return path, "", false
503         }
504         return prefix, pathMajor, true
505 }
506
507 // MatchPathMajor reports whether the semantic version v
508 // matches the path major version pathMajor.
509 //
510 // MatchPathMajor returns true if and only if CheckPathMajor returns nil.
511 func MatchPathMajor(v, pathMajor string) bool {
512         return CheckPathMajor(v, pathMajor) == nil
513 }
514
515 // CheckPathMajor returns a non-nil error if the semantic version v
516 // does not match the path major version pathMajor.
517 func CheckPathMajor(v, pathMajor string) error {
518         // TODO(jayconrod): return errors or panic for invalid inputs. This function
519         // (and others) was covered by integration tests for cmd/go, and surrounding
520         // code protected against invalid inputs like non-canonical versions.
521         if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
522                 pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
523         }
524         if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
525                 // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1.
526                 // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405.
527                 return nil
528         }
529         m := semver.Major(v)
530         if pathMajor == "" {
531                 if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
532                         return nil
533                 }
534                 pathMajor = "v0 or v1"
535         } else if pathMajor[0] == '/' || pathMajor[0] == '.' {
536                 if m == pathMajor[1:] {
537                         return nil
538                 }
539                 pathMajor = pathMajor[1:]
540         }
541         return &InvalidVersionError{
542                 Version: v,
543                 Err:     fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
544         }
545 }
546
547 // PathMajorPrefix returns the major-version tag prefix implied by pathMajor.
548 // An empty PathMajorPrefix allows either v0 or v1.
549 //
550 // Note that MatchPathMajor may accept some versions that do not actually begin
551 // with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1'
552 // pathMajor, even though that pathMajor implies 'v1' tagging.
553 func PathMajorPrefix(pathMajor string) string {
554         if pathMajor == "" {
555                 return ""
556         }
557         if pathMajor[0] != '/' && pathMajor[0] != '.' {
558                 panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
559         }
560         if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
561                 pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
562         }
563         m := pathMajor[1:]
564         if m != semver.Major(m) {
565                 panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
566         }
567         return m
568 }
569
570 // CanonicalVersion returns the canonical form of the version string v.
571 // It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
572 func CanonicalVersion(v string) string {
573         cv := semver.Canonical(v)
574         if semver.Build(v) == "+incompatible" {
575                 cv += "+incompatible"
576         }
577         return cv
578 }
579
580 // Sort sorts the list by Path, breaking ties by comparing Version fields.
581 // The Version fields are interpreted as semantic versions (using semver.Compare)
582 // optionally followed by a tie-breaking suffix introduced by a slash character,
583 // like in "v0.0.1/go.mod".
584 func Sort(list []Version) {
585         sort.Slice(list, func(i, j int) bool {
586                 mi := list[i]
587                 mj := list[j]
588                 if mi.Path != mj.Path {
589                         return mi.Path < mj.Path
590                 }
591                 // To help go.sum formatting, allow version/file.
592                 // Compare semver prefix by semver rules,
593                 // file by string order.
594                 vi := mi.Version
595                 vj := mj.Version
596                 var fi, fj string
597                 if k := strings.Index(vi, "/"); k >= 0 {
598                         vi, fi = vi[:k], vi[k:]
599                 }
600                 if k := strings.Index(vj, "/"); k >= 0 {
601                         vj, fj = vj[:k], vj[k:]
602                 }
603                 if vi != vj {
604                         return semver.Compare(vi, vj) < 0
605                 }
606                 return fi < fj
607         })
608 }
609
610 // EscapePath returns the escaped form of the given module path.
611 // It fails if the module path is invalid.
612 func EscapePath(path string) (escaped string, err error) {
613         if err := CheckPath(path); err != nil {
614                 return "", err
615         }
616
617         return escapeString(path)
618 }
619
620 // EscapeVersion returns the escaped form of the given module version.
621 // Versions are allowed to be in non-semver form but must be valid file names
622 // and not contain exclamation marks.
623 func EscapeVersion(v string) (escaped string, err error) {
624         if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
625                 return "", &InvalidVersionError{
626                         Version: v,
627                         Err:     fmt.Errorf("disallowed version string"),
628                 }
629         }
630         return escapeString(v)
631 }
632
633 func escapeString(s string) (escaped string, err error) {
634         haveUpper := false
635         for _, r := range s {
636                 if r == '!' || r >= utf8.RuneSelf {
637                         // This should be disallowed by CheckPath, but diagnose anyway.
638                         // The correctness of the escaping loop below depends on it.
639                         return "", fmt.Errorf("internal error: inconsistency in EscapePath")
640                 }
641                 if 'A' <= r && r <= 'Z' {
642                         haveUpper = true
643                 }
644         }
645
646         if !haveUpper {
647                 return s, nil
648         }
649
650         var buf []byte
651         for _, r := range s {
652                 if 'A' <= r && r <= 'Z' {
653                         buf = append(buf, '!', byte(r+'a'-'A'))
654                 } else {
655                         buf = append(buf, byte(r))
656                 }
657         }
658         return string(buf), nil
659 }
660
661 // UnescapePath returns the module path for the given escaped path.
662 // It fails if the escaped path is invalid or describes an invalid path.
663 func UnescapePath(escaped string) (path string, err error) {
664         path, ok := unescapeString(escaped)
665         if !ok {
666                 return "", fmt.Errorf("invalid escaped module path %q", escaped)
667         }
668         if err := CheckPath(path); err != nil {
669                 return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err)
670         }
671         return path, nil
672 }
673
674 // UnescapeVersion returns the version string for the given escaped version.
675 // It fails if the escaped form is invalid or describes an invalid version.
676 // Versions are allowed to be in non-semver form but must be valid file names
677 // and not contain exclamation marks.
678 func UnescapeVersion(escaped string) (v string, err error) {
679         v, ok := unescapeString(escaped)
680         if !ok {
681                 return "", fmt.Errorf("invalid escaped version %q", escaped)
682         }
683         if err := checkElem(v, true); err != nil {
684                 return "", fmt.Errorf("invalid escaped version %q: %v", v, err)
685         }
686         return v, nil
687 }
688
689 func unescapeString(escaped string) (string, bool) {
690         var buf []byte
691
692         bang := false
693         for _, r := range escaped {
694                 if r >= utf8.RuneSelf {
695                         return "", false
696                 }
697                 if bang {
698                         bang = false
699                         if r < 'a' || 'z' < r {
700                                 return "", false
701                         }
702                         buf = append(buf, byte(r+'A'-'a'))
703                         continue
704                 }
705                 if r == '!' {
706                         bang = true
707                         continue
708                 }
709                 if 'A' <= r && r <= 'Z' {
710                         return "", false
711                 }
712                 buf = append(buf, byte(r))
713         }
714         if bang {
715                 return "", false
716         }
717         return string(buf), true
718 }