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
5package modload
6
7import (
8	"errors"
9	"fmt"
10	"os"
11	pathpkg "path"
12	"path/filepath"
13	"strings"
14	"sync"
15
16	"cmd/go/internal/cfg"
17	"cmd/go/internal/imports"
18	"cmd/go/internal/modfetch"
19	"cmd/go/internal/search"
20	"cmd/go/internal/str"
21
22	"golang.org/x/mod/module"
23	"golang.org/x/mod/semver"
24)
25
26// Query looks up a revision of a given module given a version query string.
27// The module must be a complete module path.
28// The version must take one of the following forms:
29//
30// - the literal string "latest", denoting the latest available, allowed
31//   tagged version, with non-prereleases preferred over prereleases.
32//   If there are no tagged versions in the repo, latest returns the most
33//   recent commit.
34// - the literal string "upgrade", equivalent to "latest" except that if
35//   current is a newer version, current will be returned (see below).
36// - the literal string "patch", denoting the latest available tagged version
37//   with the same major and minor number as current (see below).
38// - v1, denoting the latest available tagged version v1.x.x.
39// - v1.2, denoting the latest available tagged version v1.2.x.
40// - v1.2.3, a semantic version string denoting that tagged version.
41// - <v1.2.3, <=v1.2.3, >v1.2.3, >=v1.2.3,
42//   denoting the version closest to the target and satisfying the given operator,
43//   with non-prereleases preferred over prereleases.
44// - a repository commit identifier or tag, denoting that commit.
45//
46// current denotes the current version of the module; it may be "" if the
47// current version is unknown or should not be considered. If query is
48// "upgrade" or "patch", current will be returned if it is a newer
49// semantic version or a chronologically later pseudo-version than the
50// version that would otherwise be chosen. This prevents accidental downgrades
51// from newer pre-release or development versions.
52//
53// If the allowed function is non-nil, Query excludes any versions for which
54// allowed returns false.
55//
56// If path is the path of the main module and the query is "latest",
57// Query returns Target.Version as the version.
58func Query(path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
59	var info *modfetch.RevInfo
60	err := modfetch.TryProxies(func(proxy string) (err error) {
61		info, err = queryProxy(proxy, path, query, current, allowed)
62		return err
63	})
64	return info, err
65}
66
67var errQueryDisabled error = queryDisabledError{}
68
69type queryDisabledError struct{}
70
71func (queryDisabledError) Error() string {
72	if cfg.BuildModReason == "" {
73		return fmt.Sprintf("cannot query module due to -mod=%s", cfg.BuildMod)
74	}
75	return fmt.Sprintf("cannot query module due to -mod=%s\n\t(%s)", cfg.BuildMod, cfg.BuildModReason)
76}
77
78func queryProxy(proxy, path, query, current string, allowed func(module.Version) bool) (*modfetch.RevInfo, error) {
79	if current != "" && !semver.IsValid(current) {
80		return nil, fmt.Errorf("invalid previous version %q", current)
81	}
82	if cfg.BuildMod == "vendor" {
83		return nil, errQueryDisabled
84	}
85	if allowed == nil {
86		allowed = func(module.Version) bool { return true }
87	}
88
89	// Parse query to detect parse errors (and possibly handle query)
90	// before any network I/O.
91	badVersion := func(v string) (*modfetch.RevInfo, error) {
92		return nil, fmt.Errorf("invalid semantic version %q in range %q", v, query)
93	}
94	matchesMajor := func(v string) bool {
95		_, pathMajor, ok := module.SplitPathVersion(path)
96		if !ok {
97			return false
98		}
99		return module.CheckPathMajor(v, pathMajor) == nil
100	}
101	var (
102		ok                 func(module.Version) bool
103		prefix             string
104		preferOlder        bool
105		mayUseLatest       bool
106		preferIncompatible bool = strings.HasSuffix(current, "+incompatible")
107	)
108	switch {
109	case query == "latest":
110		ok = allowed
111		mayUseLatest = true
112
113	case query == "upgrade":
114		ok = allowed
115		mayUseLatest = true
116
117	case query == "patch":
118		if current == "" {
119			ok = allowed
120			mayUseLatest = true
121		} else {
122			prefix = semver.MajorMinor(current)
123			ok = func(m module.Version) bool {
124				return matchSemverPrefix(prefix, m.Version) && allowed(m)
125			}
126		}
127
128	case strings.HasPrefix(query, "<="):
129		v := query[len("<="):]
130		if !semver.IsValid(v) {
131			return badVersion(v)
132		}
133		if isSemverPrefix(v) {
134			// Refuse to say whether <=v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
135			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
136		}
137		ok = func(m module.Version) bool {
138			return semver.Compare(m.Version, v) <= 0 && allowed(m)
139		}
140		if !matchesMajor(v) {
141			preferIncompatible = true
142		}
143
144	case strings.HasPrefix(query, "<"):
145		v := query[len("<"):]
146		if !semver.IsValid(v) {
147			return badVersion(v)
148		}
149		ok = func(m module.Version) bool {
150			return semver.Compare(m.Version, v) < 0 && allowed(m)
151		}
152		if !matchesMajor(v) {
153			preferIncompatible = true
154		}
155
156	case strings.HasPrefix(query, ">="):
157		v := query[len(">="):]
158		if !semver.IsValid(v) {
159			return badVersion(v)
160		}
161		ok = func(m module.Version) bool {
162			return semver.Compare(m.Version, v) >= 0 && allowed(m)
163		}
164		preferOlder = true
165		if !matchesMajor(v) {
166			preferIncompatible = true
167		}
168
169	case strings.HasPrefix(query, ">"):
170		v := query[len(">"):]
171		if !semver.IsValid(v) {
172			return badVersion(v)
173		}
174		if isSemverPrefix(v) {
175			// Refuse to say whether >v1.2 allows v1.2.3 (remember, @v1.2 might mean v1.2.3).
176			return nil, fmt.Errorf("ambiguous semantic version %q in range %q", v, query)
177		}
178		ok = func(m module.Version) bool {
179			return semver.Compare(m.Version, v) > 0 && allowed(m)
180		}
181		preferOlder = true
182		if !matchesMajor(v) {
183			preferIncompatible = true
184		}
185
186	case semver.IsValid(query) && isSemverPrefix(query):
187		ok = func(m module.Version) bool {
188			return matchSemverPrefix(query, m.Version) && allowed(m)
189		}
190		prefix = query + "."
191		if !matchesMajor(query) {
192			preferIncompatible = true
193		}
194
195	default:
196		// Direct lookup of semantic version or commit identifier.
197		//
198		// If the identifier is not a canonical semver tag — including if it's a
199		// semver tag with a +metadata suffix — then modfetch.Stat will populate
200		// info.Version with a suitable pseudo-version.
201		info, err := modfetch.Stat(proxy, path, query)
202		if err != nil {
203			queryErr := err
204			// The full query doesn't correspond to a tag. If it is a semantic version
205			// with a +metadata suffix, see if there is a tag without that suffix:
206			// semantic versioning defines them to be equivalent.
207			if vers := module.CanonicalVersion(query); vers != "" && vers != query {
208				info, err = modfetch.Stat(proxy, path, vers)
209				if !errors.Is(err, os.ErrNotExist) {
210					return info, err
211				}
212			}
213			if err != nil {
214				return nil, queryErr
215			}
216		}
217		if !allowed(module.Version{Path: path, Version: info.Version}) {
218			return nil, fmt.Errorf("%s@%s excluded", path, info.Version)
219		}
220		return info, nil
221	}
222
223	if path == Target.Path {
224		if query != "latest" {
225			return nil, fmt.Errorf("can't query specific version (%q) for the main module (%s)", query, path)
226		}
227		if !allowed(Target) {
228			return nil, fmt.Errorf("internal error: main module version is not allowed")
229		}
230		return &modfetch.RevInfo{Version: Target.Version}, nil
231	}
232
233	if str.HasPathPrefix(path, "std") || str.HasPathPrefix(path, "cmd") {
234		return nil, fmt.Errorf("explicit requirement on standard-library module %s not allowed", path)
235	}
236
237	// Load versions and execute query.
238	repo, err := modfetch.Lookup(proxy, path)
239	if err != nil {
240		return nil, err
241	}
242	versions, err := repo.Versions(prefix)
243	if err != nil {
244		return nil, err
245	}
246	releases, prereleases, err := filterVersions(path, versions, ok, preferIncompatible)
247	if err != nil {
248		return nil, err
249	}
250
251	lookup := func(v string) (*modfetch.RevInfo, error) {
252		rev, err := repo.Stat(v)
253		if err != nil {
254			return nil, err
255		}
256
257		// For "upgrade" and "patch", make sure we don't accidentally downgrade
258		// from a newer prerelease or from a chronologically newer pseudoversion.
259		if current != "" && (query == "upgrade" || query == "patch") {
260			currentTime, err := modfetch.PseudoVersionTime(current)
261			if semver.Compare(rev.Version, current) < 0 || (err == nil && rev.Time.Before(currentTime)) {
262				return repo.Stat(current)
263			}
264		}
265
266		return rev, nil
267	}
268
269	if preferOlder {
270		if len(releases) > 0 {
271			return lookup(releases[0])
272		}
273		if len(prereleases) > 0 {
274			return lookup(prereleases[0])
275		}
276	} else {
277		if len(releases) > 0 {
278			return lookup(releases[len(releases)-1])
279		}
280		if len(prereleases) > 0 {
281			return lookup(prereleases[len(prereleases)-1])
282		}
283	}
284
285	if mayUseLatest {
286		// Special case for "latest": if no tags match, use latest commit in repo,
287		// provided it is not excluded.
288		latest, err := repo.Latest()
289		if err == nil {
290			if allowed(module.Version{Path: path, Version: latest.Version}) {
291				return lookup(latest.Version)
292			}
293		} else if !errors.Is(err, os.ErrNotExist) {
294			return nil, err
295		}
296	}
297
298	return nil, &NoMatchingVersionError{query: query, current: current}
299}
300
301// isSemverPrefix reports whether v is a semantic version prefix: v1 or v1.2 (not v1.2.3).
302// The caller is assumed to have checked that semver.IsValid(v) is true.
303func isSemverPrefix(v string) bool {
304	dots := 0
305	for i := 0; i < len(v); i++ {
306		switch v[i] {
307		case '-', '+':
308			return false
309		case '.':
310			dots++
311			if dots >= 2 {
312				return false
313			}
314		}
315	}
316	return true
317}
318
319// matchSemverPrefix reports whether the shortened semantic version p
320// matches the full-width (non-shortened) semantic version v.
321func matchSemverPrefix(p, v string) bool {
322	return len(v) > len(p) && v[len(p)] == '.' && v[:len(p)] == p && semver.Prerelease(v) == ""
323}
324
325// filterVersions classifies versions into releases and pre-releases, filtering
326// out:
327// 	1. versions that do not satisfy the 'ok' predicate, and
328// 	2. "+incompatible" versions, if a compatible one satisfies the predicate
329// 	   and the incompatible version is not preferred.
330func filterVersions(path string, versions []string, ok func(module.Version) bool, preferIncompatible bool) (releases, prereleases []string, err error) {
331	var lastCompatible string
332	for _, v := range versions {
333		if !ok(module.Version{Path: path, Version: v}) {
334			continue
335		}
336
337		if !preferIncompatible {
338			if !strings.HasSuffix(v, "+incompatible") {
339				lastCompatible = v
340			} else if lastCompatible != "" {
341				// If the latest compatible version is allowed and has a go.mod file,
342				// ignore any version with a higher (+incompatible) major version. (See
343				// https://golang.org/issue/34165.) Note that we even prefer a
344				// compatible pre-release over an incompatible release.
345
346				ok, err := versionHasGoMod(module.Version{Path: path, Version: lastCompatible})
347				if err != nil {
348					return nil, nil, err
349				}
350				if ok {
351					break
352				}
353
354				// No acceptable compatible release has a go.mod file, so the versioning
355				// for the module might not be module-aware, and we should respect
356				// legacy major-version tags.
357				preferIncompatible = true
358			}
359		}
360
361		if semver.Prerelease(v) != "" {
362			prereleases = append(prereleases, v)
363		} else {
364			releases = append(releases, v)
365		}
366	}
367
368	return releases, prereleases, nil
369}
370
371type QueryResult struct {
372	Mod      module.Version
373	Rev      *modfetch.RevInfo
374	Packages []string
375}
376
377// QueryPackage looks up the module(s) containing path at a revision matching
378// query. The results are sorted by module path length in descending order.
379//
380// If the package is in the main module, QueryPackage considers only the main
381// module and only the version "latest", without checking for other possible
382// modules.
383func QueryPackage(path, query string, allowed func(module.Version) bool) ([]QueryResult, error) {
384	if search.IsMetaPackage(path) || strings.Contains(path, "...") {
385		return nil, fmt.Errorf("pattern %s is not an importable package", path)
386	}
387	return QueryPattern(path, query, allowed)
388}
389
390// QueryPattern looks up the module(s) containing at least one package matching
391// the given pattern at the given version. The results are sorted by module path
392// length in descending order.
393//
394// QueryPattern queries modules with package paths up to the first "..."
395// in the pattern. For the pattern "example.com/a/b.../c", QueryPattern would
396// consider prefixes of "example.com/a". If multiple modules have versions
397// that match the query and packages that match the pattern, QueryPattern
398// picks the one with the longest module path.
399//
400// If any matching package is in the main module, QueryPattern considers only
401// the main module and only the version "latest", without checking for other
402// possible modules.
403func QueryPattern(pattern, query string, allowed func(module.Version) bool) ([]QueryResult, error) {
404	base := pattern
405	var match func(m module.Version, root string, isLocal bool) (pkgs []string)
406
407	if i := strings.Index(pattern, "..."); i >= 0 {
408		base = pathpkg.Dir(pattern[:i+3])
409		match = func(m module.Version, root string, isLocal bool) []string {
410			return matchPackages(pattern, imports.AnyTags(), false, []module.Version{m})
411		}
412	} else {
413		match = func(m module.Version, root string, isLocal bool) []string {
414			prefix := m.Path
415			if m == Target {
416				prefix = targetPrefix
417			}
418			if _, ok := dirInModule(pattern, prefix, root, isLocal); ok {
419				return []string{pattern}
420			} else {
421				return nil
422			}
423		}
424	}
425
426	if HasModRoot() {
427		pkgs := match(Target, modRoot, true)
428		if len(pkgs) > 0 {
429			if query != "latest" {
430				return nil, fmt.Errorf("can't query specific version for package %s in the main module (%s)", pattern, Target.Path)
431			}
432			if !allowed(Target) {
433				return nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed", pattern, Target.Path)
434			}
435			return []QueryResult{{
436				Mod:      Target,
437				Rev:      &modfetch.RevInfo{Version: Target.Version},
438				Packages: pkgs,
439			}}, nil
440		}
441	}
442
443	var (
444		results          []QueryResult
445		candidateModules = modulePrefixesExcludingTarget(base)
446	)
447	if len(candidateModules) == 0 {
448		return nil, fmt.Errorf("package %s is not in the main module (%s)", pattern, Target.Path)
449	}
450
451	err := modfetch.TryProxies(func(proxy string) error {
452		queryModule := func(path string) (r QueryResult, err error) {
453			r.Mod.Path = path
454			r.Rev, err = queryProxy(proxy, path, query, "", allowed)
455			if err != nil {
456				return r, err
457			}
458			r.Mod.Version = r.Rev.Version
459			root, isLocal, err := fetch(r.Mod)
460			if err != nil {
461				return r, err
462			}
463			r.Packages = match(r.Mod, root, isLocal)
464			if len(r.Packages) == 0 {
465				return r, &PackageNotInModuleError{
466					Mod:         r.Mod,
467					Replacement: Replacement(r.Mod),
468					Query:       query,
469					Pattern:     pattern,
470				}
471			}
472			return r, nil
473		}
474
475		var err error
476		results, err = queryPrefixModules(candidateModules, queryModule)
477		return err
478	})
479
480	return results, err
481}
482
483// modulePrefixesExcludingTarget returns all prefixes of path that may plausibly
484// exist as a module, excluding targetPrefix but otherwise including path
485// itself, sorted by descending length.
486func modulePrefixesExcludingTarget(path string) []string {
487	prefixes := make([]string, 0, strings.Count(path, "/")+1)
488
489	for {
490		if path != targetPrefix {
491			if _, _, ok := module.SplitPathVersion(path); ok {
492				prefixes = append(prefixes, path)
493			}
494		}
495
496		j := strings.LastIndexByte(path, '/')
497		if j < 0 {
498			break
499		}
500		path = path[:j]
501	}
502
503	return prefixes
504}
505
506type prefixResult struct {
507	QueryResult
508	err error
509}
510
511func queryPrefixModules(candidateModules []string, queryModule func(path string) (QueryResult, error)) (found []QueryResult, err error) {
512	// If the path we're attempting is not in the module cache and we don't have a
513	// fetch result cached either, we'll end up making a (potentially slow)
514	// request to the proxy or (often even slower) the origin server.
515	// To minimize latency, execute all of those requests in parallel.
516	type result struct {
517		QueryResult
518		err error
519	}
520	results := make([]result, len(candidateModules))
521	var wg sync.WaitGroup
522	wg.Add(len(candidateModules))
523	for i, p := range candidateModules {
524		go func(p string, r *result) {
525			r.QueryResult, r.err = queryModule(p)
526			wg.Done()
527		}(p, &results[i])
528	}
529	wg.Wait()
530
531	// Classify the results. In case of failure, identify the error that the user
532	// is most likely to find helpful: the most useful class of error at the
533	// longest matching path.
534	var (
535		noPackage   *PackageNotInModuleError
536		noVersion   *NoMatchingVersionError
537		notExistErr error
538	)
539	for _, r := range results {
540		switch rErr := r.err.(type) {
541		case nil:
542			found = append(found, r.QueryResult)
543		case *PackageNotInModuleError:
544			if noPackage == nil {
545				noPackage = rErr
546			}
547		case *NoMatchingVersionError:
548			if noVersion == nil {
549				noVersion = rErr
550			}
551		default:
552			if errors.Is(rErr, os.ErrNotExist) {
553				if notExistErr == nil {
554					notExistErr = rErr
555				}
556			} else if err == nil {
557				if len(found) > 0 || noPackage != nil {
558					// golang.org/issue/34094: If we have already found a module that
559					// could potentially contain the target package, ignore unclassified
560					// errors for modules with shorter paths.
561
562					// golang.org/issue/34383 is a special case of this: if we have
563					// already found example.com/foo/v2@v2.0.0 with a matching go.mod
564					// file, ignore the error from example.com/foo@v2.0.0.
565				} else {
566					err = r.err
567				}
568			}
569		}
570	}
571
572	// TODO(#26232): If len(found) == 0 and some of the errors are 4xx HTTP
573	// codes, have the auth package recheck the failed paths.
574	// If we obtain new credentials for any of them, re-run the above loop.
575
576	if len(found) == 0 && err == nil {
577		switch {
578		case noPackage != nil:
579			err = noPackage
580		case noVersion != nil:
581			err = noVersion
582		case notExistErr != nil:
583			err = notExistErr
584		default:
585			panic("queryPrefixModules: no modules found, but no error detected")
586		}
587	}
588
589	return found, err
590}
591
592// A NoMatchingVersionError indicates that Query found a module at the requested
593// path, but not at any versions satisfying the query string and allow-function.
594//
595// NOTE: NoMatchingVersionError MUST NOT implement Is(os.ErrNotExist).
596//
597// If the module came from a proxy, that proxy had to return a successful status
598// code for the versions it knows about, and thus did not have the opportunity
599// to return a non-400 status code to suppress fallback.
600type NoMatchingVersionError struct {
601	query, current string
602}
603
604func (e *NoMatchingVersionError) Error() string {
605	currentSuffix := ""
606	if (e.query == "upgrade" || e.query == "patch") && e.current != "" {
607		currentSuffix = fmt.Sprintf(" (current version is %s)", e.current)
608	}
609	return fmt.Sprintf("no matching versions for query %q", e.query) + currentSuffix
610}
611
612// A PackageNotInModuleError indicates that QueryPattern found a candidate
613// module at the requested version, but that module did not contain any packages
614// matching the requested pattern.
615//
616// NOTE: PackageNotInModuleError MUST NOT implement Is(os.ErrNotExist).
617//
618// If the module came from a proxy, that proxy had to return a successful status
619// code for the versions it knows about, and thus did not have the opportunity
620// to return a non-400 status code to suppress fallback.
621type PackageNotInModuleError struct {
622	Mod         module.Version
623	Replacement module.Version
624	Query       string
625	Pattern     string
626}
627
628func (e *PackageNotInModuleError) Error() string {
629	found := ""
630	if r := e.Replacement; r.Path != "" {
631		replacement := r.Path
632		if r.Version != "" {
633			replacement = fmt.Sprintf("%s@%s", r.Path, r.Version)
634		}
635		if e.Query == e.Mod.Version {
636			found = fmt.Sprintf(" (replaced by %s)", replacement)
637		} else {
638			found = fmt.Sprintf(" (%s, replaced by %s)", e.Mod.Version, replacement)
639		}
640	} else if e.Query != e.Mod.Version {
641		found = fmt.Sprintf(" (%s)", e.Mod.Version)
642	}
643
644	if strings.Contains(e.Pattern, "...") {
645		return fmt.Sprintf("module %s@%s found%s, but does not contain packages matching %s", e.Mod.Path, e.Query, found, e.Pattern)
646	}
647	return fmt.Sprintf("module %s@%s found%s, but does not contain package %s", e.Mod.Path, e.Query, found, e.Pattern)
648}
649
650// ModuleHasRootPackage returns whether module m contains a package m.Path.
651func ModuleHasRootPackage(m module.Version) (bool, error) {
652	root, isLocal, err := fetch(m)
653	if err != nil {
654		return false, err
655	}
656	_, ok := dirInModule(m.Path, m.Path, root, isLocal)
657	return ok, nil
658}
659
660func versionHasGoMod(m module.Version) (bool, error) {
661	root, _, err := fetch(m)
662	if err != nil {
663		return false, err
664	}
665	fi, err := os.Stat(filepath.Join(root, "go.mod"))
666	return err == nil && !fi.IsDir(), nil
667}
668