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	"context"
9	"encoding/hex"
10	"errors"
11	"fmt"
12	"internal/goroot"
13	"io/fs"
14	"os"
15	"path/filepath"
16	"strings"
17
18	"cmd/go/internal/base"
19	"cmd/go/internal/cfg"
20	"cmd/go/internal/modfetch"
21	"cmd/go/internal/modinfo"
22	"cmd/go/internal/search"
23
24	"golang.org/x/mod/module"
25	"golang.org/x/mod/semver"
26)
27
28var (
29	infoStart, _ = hex.DecodeString("3077af0c9274080241e1c107e6d618e6")
30	infoEnd, _   = hex.DecodeString("f932433186182072008242104116d8f2")
31)
32
33func isStandardImportPath(path string) bool {
34	return findStandardImportPath(path) != ""
35}
36
37func findStandardImportPath(path string) string {
38	if path == "" {
39		panic("findStandardImportPath called with empty path")
40	}
41	if search.IsStandardImportPath(path) {
42		if goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
43			return filepath.Join(cfg.GOROOT, "src", path)
44		}
45	}
46	return ""
47}
48
49// PackageModuleInfo returns information about the module that provides
50// a given package. If modules are not enabled or if the package is in the
51// standard library or if the package was not successfully loaded with
52// LoadPackages or ImportFromFiles, nil is returned.
53func PackageModuleInfo(ctx context.Context, pkgpath string) *modinfo.ModulePublic {
54	if isStandardImportPath(pkgpath) || !Enabled() {
55		return nil
56	}
57	m, ok := findModule(loaded, pkgpath)
58	if !ok {
59		return nil
60	}
61
62	rs := LoadModFile(ctx)
63	return moduleInfo(ctx, rs, m, 0)
64}
65
66func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
67	if !Enabled() {
68		return nil
69	}
70
71	if i := strings.Index(path, "@"); i >= 0 {
72		m := module.Version{Path: path[:i], Version: path[i+1:]}
73		return moduleInfo(ctx, nil, m, 0)
74	}
75
76	rs := LoadModFile(ctx)
77
78	var (
79		v  string
80		ok bool
81	)
82	if rs.pruning == pruned {
83		v, ok = rs.rootSelected(path)
84	}
85	if !ok {
86		mg, err := rs.Graph(ctx)
87		if err != nil {
88			base.Fatalf("go: %v", err)
89		}
90		v = mg.Selected(path)
91	}
92
93	if v == "none" {
94		return &modinfo.ModulePublic{
95			Path: path,
96			Error: &modinfo.ModuleError{
97				Err: "module not in current build",
98			},
99		}
100	}
101
102	return moduleInfo(ctx, rs, module.Version{Path: path, Version: v}, 0)
103}
104
105// addUpdate fills in m.Update if an updated version is available.
106func addUpdate(ctx context.Context, m *modinfo.ModulePublic) {
107	if m.Version == "" {
108		return
109	}
110
111	info, err := Query(ctx, m.Path, "upgrade", m.Version, CheckAllowed)
112	var noVersionErr *NoMatchingVersionError
113	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
114		// Ignore "not found" and "no matching version" errors.
115		// This means the proxy has no matching version or no versions at all.
116		//
117		// We should report other errors though. An attacker that controls the
118		// network shouldn't be able to hide versions by interfering with
119		// the HTTPS connection. An attacker that controls the proxy may still
120		// hide versions, since the "list" and "latest" endpoints are not
121		// authenticated.
122		return
123	} else if err != nil {
124		if m.Error == nil {
125			m.Error = &modinfo.ModuleError{Err: err.Error()}
126		}
127		return
128	}
129
130	if semver.Compare(info.Version, m.Version) > 0 {
131		m.Update = &modinfo.ModulePublic{
132			Path:    m.Path,
133			Version: info.Version,
134			Time:    &info.Time,
135		}
136	}
137}
138
139// addVersions fills in m.Versions with the list of known versions.
140// Excluded versions will be omitted. If listRetracted is false, retracted
141// versions will also be omitted.
142func addVersions(ctx context.Context, m *modinfo.ModulePublic, listRetracted bool) {
143	allowed := CheckAllowed
144	if listRetracted {
145		allowed = CheckExclusions
146	}
147	var err error
148	m.Versions, err = versions(ctx, m.Path, allowed)
149	if err != nil && m.Error == nil {
150		m.Error = &modinfo.ModuleError{Err: err.Error()}
151	}
152}
153
154// addRetraction fills in m.Retracted if the module was retracted by its author.
155// m.Error is set if there's an error loading retraction information.
156func addRetraction(ctx context.Context, m *modinfo.ModulePublic) {
157	if m.Version == "" {
158		return
159	}
160
161	err := CheckRetractions(ctx, module.Version{Path: m.Path, Version: m.Version})
162	var noVersionErr *NoMatchingVersionError
163	var retractErr *ModuleRetractedError
164	if err == nil || errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
165		// Ignore "not found" and "no matching version" errors.
166		// This means the proxy has no matching version or no versions at all.
167		//
168		// We should report other errors though. An attacker that controls the
169		// network shouldn't be able to hide versions by interfering with
170		// the HTTPS connection. An attacker that controls the proxy may still
171		// hide versions, since the "list" and "latest" endpoints are not
172		// authenticated.
173		return
174	} else if errors.As(err, &retractErr) {
175		if len(retractErr.Rationale) == 0 {
176			m.Retracted = []string{"retracted by module author"}
177		} else {
178			m.Retracted = retractErr.Rationale
179		}
180	} else if m.Error == nil {
181		m.Error = &modinfo.ModuleError{Err: err.Error()}
182	}
183}
184
185// addDeprecation fills in m.Deprecated if the module was deprecated by its
186// author. m.Error is set if there's an error loading deprecation information.
187func addDeprecation(ctx context.Context, m *modinfo.ModulePublic) {
188	deprecation, err := CheckDeprecation(ctx, module.Version{Path: m.Path, Version: m.Version})
189	var noVersionErr *NoMatchingVersionError
190	if errors.Is(err, fs.ErrNotExist) || errors.As(err, &noVersionErr) {
191		// Ignore "not found" and "no matching version" errors.
192		// This means the proxy has no matching version or no versions at all.
193		//
194		// We should report other errors though. An attacker that controls the
195		// network shouldn't be able to hide versions by interfering with
196		// the HTTPS connection. An attacker that controls the proxy may still
197		// hide versions, since the "list" and "latest" endpoints are not
198		// authenticated.
199		return
200	}
201	if err != nil {
202		if m.Error == nil {
203			m.Error = &modinfo.ModuleError{Err: err.Error()}
204		}
205		return
206	}
207	m.Deprecated = deprecation
208}
209
210// moduleInfo returns information about module m, loaded from the requirements
211// in rs (which may be nil to indicate that m was not loaded from a requirement
212// graph).
213func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
214	if m.Version == "" && MainModules.Contains(m.Path) {
215		info := &modinfo.ModulePublic{
216			Path:    m.Path,
217			Version: m.Version,
218			Main:    true,
219		}
220		if v, ok := rawGoVersion.Load(m); ok {
221			info.GoVersion = v.(string)
222		} else {
223			panic("internal error: GoVersion not set for main module")
224		}
225		if modRoot := MainModules.ModRoot(m); modRoot != "" {
226			info.Dir = modRoot
227			info.GoMod = modFilePath(modRoot)
228		}
229		return info
230	}
231
232	info := &modinfo.ModulePublic{
233		Path:     m.Path,
234		Version:  m.Version,
235		Indirect: rs != nil && !rs.direct[m.Path],
236	}
237	if v, ok := rawGoVersion.Load(m); ok {
238		info.GoVersion = v.(string)
239	}
240
241	// completeFromModCache fills in the extra fields in m using the module cache.
242	completeFromModCache := func(m *modinfo.ModulePublic) {
243		checksumOk := func(suffix string) bool {
244			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
245				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
246		}
247
248		if m.Version != "" {
249			if q, err := Query(ctx, m.Path, m.Version, "", nil); err != nil {
250				m.Error = &modinfo.ModuleError{Err: err.Error()}
251			} else {
252				m.Version = q.Version
253				m.Time = &q.Time
254			}
255		}
256		mod := module.Version{Path: m.Path, Version: m.Version}
257
258		if m.GoVersion == "" && checksumOk("/go.mod") {
259			// Load the go.mod file to determine the Go version, since it hasn't
260			// already been populated from rawGoVersion.
261			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
262				m.GoVersion = summary.goVersion
263			}
264		}
265
266		if m.Version != "" {
267			if checksumOk("/go.mod") {
268				gomod, err := modfetch.CachePath(mod, "mod")
269				if err == nil {
270					if info, err := os.Stat(gomod); err == nil && info.Mode().IsRegular() {
271						m.GoMod = gomod
272					}
273				}
274			}
275			if checksumOk("") {
276				dir, err := modfetch.DownloadDir(mod)
277				if err == nil {
278					m.Dir = dir
279				}
280			}
281
282			if mode&ListRetracted != 0 {
283				addRetraction(ctx, m)
284			}
285		}
286	}
287
288	if rs == nil {
289		// If this was an explicitly-versioned argument to 'go mod download' or
290		// 'go list -m', report the actual requested version, not its replacement.
291		completeFromModCache(info) // Will set m.Error in vendor mode.
292		return info
293	}
294
295	r := Replacement(m)
296	if r.Path == "" {
297		if cfg.BuildMod == "vendor" {
298			// It's tempting to fill in the "Dir" field to point within the vendor
299			// directory, but that would be misleading: the vendor directory contains
300			// a flattened package tree, not complete modules, and it can even
301			// interleave packages from different modules if one module path is a
302			// prefix of the other.
303		} else {
304			completeFromModCache(info)
305		}
306		return info
307	}
308
309	// Don't hit the network to fill in extra data for replaced modules.
310	// The original resolved Version and Time don't matter enough to be
311	// worth the cost, and we're going to overwrite the GoMod and Dir from the
312	// replacement anyway. See https://golang.org/issue/27859.
313	info.Replace = &modinfo.ModulePublic{
314		Path:    r.Path,
315		Version: r.Version,
316	}
317	if v, ok := rawGoVersion.Load(m); ok {
318		info.Replace.GoVersion = v.(string)
319	}
320	if r.Version == "" {
321		if filepath.IsAbs(r.Path) {
322			info.Replace.Dir = r.Path
323		} else {
324			info.Replace.Dir = filepath.Join(replaceRelativeTo(), r.Path)
325		}
326		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
327	}
328	if cfg.BuildMod != "vendor" {
329		completeFromModCache(info.Replace)
330		info.Dir = info.Replace.Dir
331		info.GoMod = info.Replace.GoMod
332		info.Retracted = info.Replace.Retracted
333	}
334	info.GoVersion = info.Replace.GoVersion
335	return info
336}
337
338// findModule searches for the module that contains the package at path.
339// If the package was loaded, its containing module and true are returned.
340// Otherwise, module.Version{} and false are returned.
341func findModule(ld *loader, path string) (module.Version, bool) {
342	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
343		return pkg.mod, pkg.mod != module.Version{}
344	}
345	return module.Version{}, false
346}
347
348func ModInfoProg(info string, isgccgo bool) []byte {
349	// Inject an init function to set runtime.modinfo.
350	// This is only used for gccgo - with gc we hand the info directly to the linker.
351	// The init function has the drawback that packages may want to
352	// look at the module info in their init functions (see issue 29628),
353	// which won't work. See also issue 30344.
354	if isgccgo {
355		return []byte(fmt.Sprintf(`package main
356import _ "unsafe"
357//go:linkname __set_debug_modinfo__ runtime.setmodinfo
358func __set_debug_modinfo__(string)
359func init() { __set_debug_modinfo__(%q) }
360`, ModInfoData(info)))
361	}
362	return nil
363}
364
365func ModInfoData(info string) []byte {
366	return []byte(string(infoStart) + info + string(infoEnd))
367}
368