Source file src/pkg/cmd/go/internal/module/module.go
1
2
3
4
5
6
7 package module
8
9
10
11
12
13
14
15
16
17
18
19
20 import (
21 "errors"
22 "fmt"
23 "sort"
24 "strings"
25 "unicode"
26 "unicode/utf8"
27
28 "cmd/go/internal/semver"
29 )
30
31
32 type Version struct {
33 Path string
34
35
36
37
38
39
40
41 Version string `json:",omitempty"`
42 }
43
44
45 type ModuleError struct {
46 Path string
47 Version string
48 Err error
49 }
50
51
52 func VersionError(v Version, err error) error {
53 return &ModuleError{
54 Path: v.Path,
55 Version: v.Version,
56 Err: err,
57 }
58 }
59
60 func (e *ModuleError) Error() string {
61 if v, ok := e.Err.(*InvalidVersionError); ok {
62 return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err)
63 }
64 if e.Version != "" {
65 return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err)
66 }
67 return fmt.Sprintf("module %s: %v", e.Path, e.Err)
68 }
69
70 func (e *ModuleError) Unwrap() error { return e.Err }
71
72
73
74
75
76
77 type InvalidVersionError struct {
78 Version string
79 Pseudo bool
80 Err error
81 }
82
83
84
85 func (e *InvalidVersionError) noun() string {
86 if e.Pseudo {
87 return "pseudo-version"
88 }
89 return "version"
90 }
91
92 func (e *InvalidVersionError) Error() string {
93 return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err)
94 }
95
96 func (e *InvalidVersionError) Unwrap() error { return e.Err }
97
98
99
100
101
102
103
104 func Check(path, version string) error {
105 if err := CheckPath(path); err != nil {
106 return err
107 }
108 if !semver.IsValid(version) {
109 return &ModuleError{
110 Path: path,
111 Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")},
112 }
113 }
114 _, pathMajor, _ := SplitPathVersion(path)
115 if err := MatchPathMajor(version, pathMajor); err != nil {
116 return &ModuleError{Path: path, Err: err}
117 }
118 return nil
119 }
120
121
122
123
124 func firstPathOK(r rune) bool {
125 return r == '-' || r == '.' ||
126 '0' <= r && r <= '9' ||
127 'a' <= r && r <= 'z'
128 }
129
130
131
132
133
134
135 func pathOK(r rune) bool {
136 if r < utf8.RuneSelf {
137 return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' ||
138 '0' <= r && r <= '9' ||
139 'A' <= r && r <= 'Z' ||
140 'a' <= r && r <= 'z'
141 }
142 return false
143 }
144
145
146
147
148
149
150 func fileNameOK(r rune) bool {
151 if r < utf8.RuneSelf {
152
153
154
155
156
157
158 const allowed = "!#$%&()+,-.=@[]^_{}~ "
159 if '0' <= r && r <= '9' || 'A' <= r && r <= 'Z' || 'a' <= r && r <= 'z' {
160 return true
161 }
162 for i := 0; i < len(allowed); i++ {
163 if rune(allowed[i]) == r {
164 return true
165 }
166 }
167 return false
168 }
169
170
171 return unicode.IsLetter(r)
172 }
173
174
175 func CheckPath(path string) error {
176 if err := checkPath(path, false); err != nil {
177 return fmt.Errorf("malformed module path %q: %v", path, err)
178 }
179 i := strings.Index(path, "/")
180 if i < 0 {
181 i = len(path)
182 }
183 if i == 0 {
184 return fmt.Errorf("malformed module path %q: leading slash", path)
185 }
186 if !strings.Contains(path[:i], ".") {
187 return fmt.Errorf("malformed module path %q: missing dot in first path element", path)
188 }
189 if path[0] == '-' {
190 return fmt.Errorf("malformed module path %q: leading dash in first path element", path)
191 }
192 for _, r := range path[:i] {
193 if !firstPathOK(r) {
194 return fmt.Errorf("malformed module path %q: invalid char %q in first path element", path, r)
195 }
196 }
197 if _, _, ok := SplitPathVersion(path); !ok {
198 return fmt.Errorf("malformed module path %q: invalid version", path)
199 }
200 return nil
201 }
202
203
204 func CheckImportPath(path string) error {
205 if err := checkPath(path, false); err != nil {
206 return fmt.Errorf("malformed import path %q: %v", path, err)
207 }
208 return nil
209 }
210
211
212
213
214
215
216
217 func checkPath(path string, fileName bool) error {
218 if !utf8.ValidString(path) {
219 return fmt.Errorf("invalid UTF-8")
220 }
221 if path == "" {
222 return fmt.Errorf("empty string")
223 }
224 if path[0] == '-' {
225 return fmt.Errorf("leading dash")
226 }
227 if strings.Contains(path, "..") {
228 return fmt.Errorf("double dot")
229 }
230 if strings.Contains(path, "//") {
231 return fmt.Errorf("double slash")
232 }
233 if path[len(path)-1] == '/' {
234 return fmt.Errorf("trailing slash")
235 }
236 elemStart := 0
237 for i, r := range path {
238 if r == '/' {
239 if err := checkElem(path[elemStart:i], fileName); err != nil {
240 return err
241 }
242 elemStart = i + 1
243 }
244 }
245 if err := checkElem(path[elemStart:], fileName); err != nil {
246 return err
247 }
248 return nil
249 }
250
251
252
253 func checkElem(elem string, fileName bool) error {
254 if elem == "" {
255 return fmt.Errorf("empty path element")
256 }
257 if strings.Count(elem, ".") == len(elem) {
258 return fmt.Errorf("invalid path element %q", elem)
259 }
260 if elem[0] == '.' && !fileName {
261 return fmt.Errorf("leading dot in path element")
262 }
263 if elem[len(elem)-1] == '.' {
264 return fmt.Errorf("trailing dot in path element")
265 }
266 charOK := pathOK
267 if fileName {
268 charOK = fileNameOK
269 }
270 for _, r := range elem {
271 if !charOK(r) {
272 return fmt.Errorf("invalid char %q", r)
273 }
274 }
275
276
277
278 short := elem
279 if i := strings.Index(short, "."); i >= 0 {
280 short = short[:i]
281 }
282 for _, bad := range badWindowsNames {
283 if strings.EqualFold(bad, short) {
284 return fmt.Errorf("%q disallowed as path element component on Windows", short)
285 }
286 }
287 return nil
288 }
289
290
291 func CheckFilePath(path string) error {
292 if err := checkPath(path, true); err != nil {
293 return fmt.Errorf("malformed file path %q: %v", path, err)
294 }
295 return nil
296 }
297
298
299
300 var badWindowsNames = []string{
301 "CON",
302 "PRN",
303 "AUX",
304 "NUL",
305 "COM1",
306 "COM2",
307 "COM3",
308 "COM4",
309 "COM5",
310 "COM6",
311 "COM7",
312 "COM8",
313 "COM9",
314 "LPT1",
315 "LPT2",
316 "LPT3",
317 "LPT4",
318 "LPT5",
319 "LPT6",
320 "LPT7",
321 "LPT8",
322 "LPT9",
323 }
324
325
326
327
328
329 func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) {
330 if strings.HasPrefix(path, "gopkg.in/") {
331 return splitGopkgIn(path)
332 }
333
334 i := len(path)
335 dot := false
336 for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9' || path[i-1] == '.') {
337 if path[i-1] == '.' {
338 dot = true
339 }
340 i--
341 }
342 if i <= 1 || i == len(path) || path[i-1] != 'v' || path[i-2] != '/' {
343 return path, "", true
344 }
345 prefix, pathMajor = path[:i-2], path[i-2:]
346 if dot || len(pathMajor) <= 2 || pathMajor[2] == '0' || pathMajor == "/v1" {
347 return path, "", false
348 }
349 return prefix, pathMajor, true
350 }
351
352
353 func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) {
354 if !strings.HasPrefix(path, "gopkg.in/") {
355 return path, "", false
356 }
357 i := len(path)
358 if strings.HasSuffix(path, "-unstable") {
359 i -= len("-unstable")
360 }
361 for i > 0 && ('0' <= path[i-1] && path[i-1] <= '9') {
362 i--
363 }
364 if i <= 1 || path[i-1] != 'v' || path[i-2] != '.' {
365
366 return path, "", false
367 }
368 prefix, pathMajor = path[:i-2], path[i-2:]
369 if len(pathMajor) <= 2 || pathMajor[2] == '0' && pathMajor != ".v0" {
370 return path, "", false
371 }
372 return prefix, pathMajor, true
373 }
374
375
376
377 func MatchPathMajor(v, pathMajor string) error {
378 if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
379 pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
380 }
381 if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" {
382
383
384 return nil
385 }
386 m := semver.Major(v)
387 if pathMajor == "" {
388 if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" {
389 return nil
390 }
391 pathMajor = "v0 or v1"
392 } else if pathMajor[0] == '/' || pathMajor[0] == '.' {
393 if m == pathMajor[1:] {
394 return nil
395 }
396 pathMajor = pathMajor[1:]
397 }
398 return &InvalidVersionError{
399 Version: v,
400 Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)),
401 }
402 }
403
404
405
406
407
408
409
410 func PathMajorPrefix(pathMajor string) string {
411 if pathMajor == "" {
412 return ""
413 }
414 if pathMajor[0] != '/' && pathMajor[0] != '.' {
415 panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator")
416 }
417 if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") {
418 pathMajor = strings.TrimSuffix(pathMajor, "-unstable")
419 }
420 m := pathMajor[1:]
421 if m != semver.Major(m) {
422 panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version")
423 }
424 return m
425 }
426
427
428
429 func CanonicalVersion(v string) string {
430 cv := semver.Canonical(v)
431 if semver.Build(v) == "+incompatible" {
432 cv += "+incompatible"
433 }
434 return cv
435 }
436
437
438 func Sort(list []Version) {
439 sort.Slice(list, func(i, j int) bool {
440 mi := list[i]
441 mj := list[j]
442 if mi.Path != mj.Path {
443 return mi.Path < mj.Path
444 }
445
446
447
448 vi := mi.Version
449 vj := mj.Version
450 var fi, fj string
451 if k := strings.Index(vi, "/"); k >= 0 {
452 vi, fi = vi[:k], vi[k:]
453 }
454 if k := strings.Index(vj, "/"); k >= 0 {
455 vj, fj = vj[:k], vj[k:]
456 }
457 if vi != vj {
458 return semver.Compare(vi, vj) < 0
459 }
460 return fi < fj
461 })
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526 func EncodePath(path string) (encoding string, err error) {
527 if err := CheckPath(path); err != nil {
528 return "", err
529 }
530
531 return encodeString(path)
532 }
533
534
535
536
537 func EncodeVersion(v string) (encoding string, err error) {
538 if err := checkElem(v, true); err != nil || strings.Contains(v, "!") {
539 return "", &InvalidVersionError{
540 Version: v,
541 Err: fmt.Errorf("disallowed version string"),
542 }
543 }
544 return encodeString(v)
545 }
546
547 func encodeString(s string) (encoding string, err error) {
548 haveUpper := false
549 for _, r := range s {
550 if r == '!' || r >= utf8.RuneSelf {
551
552
553 return "", fmt.Errorf("internal error: inconsistency in EncodePath")
554 }
555 if 'A' <= r && r <= 'Z' {
556 haveUpper = true
557 }
558 }
559
560 if !haveUpper {
561 return s, nil
562 }
563
564 var buf []byte
565 for _, r := range s {
566 if 'A' <= r && r <= 'Z' {
567 buf = append(buf, '!', byte(r+'a'-'A'))
568 } else {
569 buf = append(buf, byte(r))
570 }
571 }
572 return string(buf), nil
573 }
574
575
576
577 func DecodePath(encoding string) (path string, err error) {
578 path, ok := decodeString(encoding)
579 if !ok {
580 return "", fmt.Errorf("invalid module path encoding %q", encoding)
581 }
582 if err := CheckPath(path); err != nil {
583 return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err)
584 }
585 return path, nil
586 }
587
588
589
590
591
592 func DecodeVersion(encoding string) (v string, err error) {
593 v, ok := decodeString(encoding)
594 if !ok {
595 return "", fmt.Errorf("invalid version encoding %q", encoding)
596 }
597 if err := checkElem(v, true); err != nil {
598 return "", fmt.Errorf("disallowed version string %q", v)
599 }
600 return v, nil
601 }
602
603 func decodeString(encoding string) (string, bool) {
604 var buf []byte
605
606 bang := false
607 for _, r := range encoding {
608 if r >= utf8.RuneSelf {
609 return "", false
610 }
611 if bang {
612 bang = false
613 if r < 'a' || 'z' < r {
614 return "", false
615 }
616 buf = append(buf, byte(r+'A'-'a'))
617 continue
618 }
619 if r == '!' {
620 bang = true
621 continue
622 }
623 if 'A' <= r && r <= 'Z' {
624 return "", false
625 }
626 buf = append(buf, byte(r))
627 }
628 if bang {
629 return "", false
630 }
631 return string(buf), true
632 }
633
View as plain text