blob: 517a88554282de5f133b8fc1196e112abf5a0d87 [file] [log] [blame]
Dan Willemsenc7413322018-08-27 23:21:26 -07001// 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 main_test
6
7import (
8 "archive/zip"
9 "bytes"
10 "encoding/json"
Colin Cross430342c2019-09-07 08:36:04 -070011 "errors"
Dan Willemsenc7413322018-08-27 23:21:26 -070012 "flag"
13 "fmt"
Colin Cross430342c2019-09-07 08:36:04 -070014 "io"
Colin Cross1f805522021-05-14 11:10:59 -070015 "io/fs"
Dan Willemsenc7413322018-08-27 23:21:26 -070016 "log"
17 "net"
18 "net/http"
19 "os"
20 "path/filepath"
Colin Cross430342c2019-09-07 08:36:04 -070021 "strconv"
Dan Willemsenc7413322018-08-27 23:21:26 -070022 "strings"
23 "sync"
24 "testing"
25
Dan Willemsenc7413322018-08-27 23:21:26 -070026 "cmd/go/internal/modfetch/codehost"
Dan Willemsenc7413322018-08-27 23:21:26 -070027 "cmd/go/internal/par"
Patrice Arruda748609c2020-06-25 12:12:21 -070028
29 "golang.org/x/mod/module"
30 "golang.org/x/mod/semver"
31 "golang.org/x/mod/sumdb"
32 "golang.org/x/mod/sumdb/dirhash"
Dan Willemsenbc60c3c2021-12-15 01:09:00 -080033 "golang.org/x/tools/txtar"
Dan Willemsenc7413322018-08-27 23:21:26 -070034)
35
36var (
37 proxyAddr = flag.String("proxy", "", "run proxy on this network address instead of running any tests")
38 proxyURL string
39)
40
41var proxyOnce sync.Once
42
43// StartProxy starts the Go module proxy running on *proxyAddr (like "localhost:1234")
44// and sets proxyURL to the GOPROXY setting to use to access the proxy.
45// Subsequent calls are no-ops.
46//
47// The proxy serves from testdata/mod. See testdata/mod/README.
48func StartProxy() {
49 proxyOnce.Do(func() {
Dan Willemsenc7413322018-08-27 23:21:26 -070050 readModList()
51 addr := *proxyAddr
52 if addr == "" {
53 addr = "localhost:0"
54 }
55 l, err := net.Listen("tcp", addr)
56 if err != nil {
57 log.Fatal(err)
58 }
59 *proxyAddr = l.Addr().String()
60 proxyURL = "http://" + *proxyAddr + "/mod"
61 fmt.Fprintf(os.Stderr, "go test proxy running at GOPROXY=%s\n", proxyURL)
62 go func() {
63 log.Fatalf("go proxy: http.Serve: %v", http.Serve(l, http.HandlerFunc(proxyHandler)))
64 }()
Colin Cross430342c2019-09-07 08:36:04 -070065
66 // Prepopulate main sumdb.
67 for _, mod := range modList {
Patrice Arruda748609c2020-06-25 12:12:21 -070068 sumdbOps.Lookup(nil, mod)
Colin Cross430342c2019-09-07 08:36:04 -070069 }
Dan Willemsenc7413322018-08-27 23:21:26 -070070 })
71}
72
73var modList []module.Version
74
75func readModList() {
Colin Cross1f805522021-05-14 11:10:59 -070076 files, err := os.ReadDir("testdata/mod")
Dan Willemsenc7413322018-08-27 23:21:26 -070077 if err != nil {
78 log.Fatal(err)
79 }
Colin Cross1f805522021-05-14 11:10:59 -070080 for _, f := range files {
81 name := f.Name()
Dan Willemsenc7413322018-08-27 23:21:26 -070082 if !strings.HasSuffix(name, ".txt") {
83 continue
84 }
85 name = strings.TrimSuffix(name, ".txt")
86 i := strings.LastIndex(name, "_v")
87 if i < 0 {
88 continue
89 }
Colin Crossd9c6b802019-03-19 21:10:31 -070090 encPath := strings.ReplaceAll(name[:i], "_", "/")
Patrice Arruda748609c2020-06-25 12:12:21 -070091 path, err := module.UnescapePath(encPath)
Dan Willemsenc7413322018-08-27 23:21:26 -070092 if err != nil {
Colin Cross1f805522021-05-14 11:10:59 -070093 if testing.Verbose() && encPath != "example.com/invalidpath/v1" {
Colin Cross430342c2019-09-07 08:36:04 -070094 fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
95 }
Dan Willemsenc7413322018-08-27 23:21:26 -070096 continue
97 }
98 encVers := name[i+1:]
Patrice Arruda748609c2020-06-25 12:12:21 -070099 vers, err := module.UnescapeVersion(encVers)
Dan Willemsenc7413322018-08-27 23:21:26 -0700100 if err != nil {
101 fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
102 continue
103 }
104 modList = append(modList, module.Version{Path: path, Version: vers})
105 }
106}
107
108var zipCache par.Cache
109
Colin Cross430342c2019-09-07 08:36:04 -0700110const (
111 testSumDBName = "localhost.localdev/sumdb"
112 testSumDBVerifierKey = "localhost.localdev/sumdb+00000c67+AcTrnkbUA+TU4heY3hkjiSES/DSQniBqIeQ/YppAUtK6"
113 testSumDBSignerKey = "PRIVATE+KEY+localhost.localdev/sumdb+00000c67+AXu6+oaVaOYuQOFrf1V59JK1owcFlJcHwwXHDfDGxSPk"
114)
115
Patrice Arruda748609c2020-06-25 12:12:21 -0700116var (
117 sumdbOps = sumdb.NewTestServer(testSumDBSignerKey, proxyGoSum)
118 sumdbServer = sumdb.NewServer(sumdbOps)
119
120 sumdbWrongOps = sumdb.NewTestServer(testSumDBSignerKey, proxyGoSumWrong)
121 sumdbWrongServer = sumdb.NewServer(sumdbWrongOps)
122)
Colin Cross430342c2019-09-07 08:36:04 -0700123
Dan Willemsenc7413322018-08-27 23:21:26 -0700124// proxyHandler serves the Go module proxy protocol.
125// See the proxy section of https://research.swtch.com/vgo-module.
126func proxyHandler(w http.ResponseWriter, r *http.Request) {
127 if !strings.HasPrefix(r.URL.Path, "/mod/") {
128 http.NotFound(w, r)
129 return
130 }
Colin Cross430342c2019-09-07 08:36:04 -0700131 path := r.URL.Path[len("/mod/"):]
132
Colin Cross1f805522021-05-14 11:10:59 -0700133 // /mod/invalid returns faulty responses.
134 if strings.HasPrefix(path, "invalid/") {
135 w.Write([]byte("invalid"))
136 return
Colin Cross430342c2019-09-07 08:36:04 -0700137 }
138
139 // Next element may opt into special behavior.
140 if j := strings.Index(path, "/"); j >= 0 {
141 n, err := strconv.Atoi(path[:j])
142 if err == nil && n >= 200 {
143 w.WriteHeader(n)
144 return
145 }
146 if strings.HasPrefix(path, "sumdb-") {
147 n, err := strconv.Atoi(path[len("sumdb-"):j])
148 if err == nil && n >= 200 {
149 if strings.HasPrefix(path[j:], "/sumdb/") {
150 w.WriteHeader(n)
151 return
152 }
153 path = path[j+1:]
154 }
155 }
156 }
157
158 // Request for $GOPROXY/sumdb-direct is direct sumdb access.
159 // (Client thinks it is talking directly to a sumdb.)
160 if strings.HasPrefix(path, "sumdb-direct/") {
161 r.URL.Path = path[len("sumdb-direct"):]
Patrice Arruda748609c2020-06-25 12:12:21 -0700162 sumdbServer.ServeHTTP(w, r)
Colin Cross430342c2019-09-07 08:36:04 -0700163 return
164 }
165
166 // Request for $GOPROXY/sumdb-wrong is direct sumdb access
167 // but all the hashes are wrong.
168 // (Client thinks it is talking directly to a sumdb.)
169 if strings.HasPrefix(path, "sumdb-wrong/") {
170 r.URL.Path = path[len("sumdb-wrong"):]
Patrice Arruda748609c2020-06-25 12:12:21 -0700171 sumdbWrongServer.ServeHTTP(w, r)
Colin Cross430342c2019-09-07 08:36:04 -0700172 return
173 }
174
Patrice Arruda4fc930d2020-08-03 23:20:51 +0000175 // Request for $GOPROXY/redirect/<count>/... goes to redirects.
176 if strings.HasPrefix(path, "redirect/") {
177 path = path[len("redirect/"):]
178 if j := strings.Index(path, "/"); j >= 0 {
179 count, err := strconv.Atoi(path[:j])
180 if err != nil {
181 return
182 }
183
184 // The last redirect.
185 if count <= 1 {
186 http.Redirect(w, r, fmt.Sprintf("/mod/%s", path[j+1:]), 302)
187 return
188 }
189 http.Redirect(w, r, fmt.Sprintf("/mod/redirect/%d/%s", count-1, path[j+1:]), 302)
190 return
191 }
192 }
193
Colin Cross430342c2019-09-07 08:36:04 -0700194 // Request for $GOPROXY/sumdb/<name>/supported
195 // is checking whether it's OK to access sumdb via the proxy.
196 if path == "sumdb/"+testSumDBName+"/supported" {
197 w.WriteHeader(200)
198 return
199 }
200
201 // Request for $GOPROXY/sumdb/<name>/... goes to sumdb.
202 if sumdbPrefix := "sumdb/" + testSumDBName + "/"; strings.HasPrefix(path, sumdbPrefix) {
203 r.URL.Path = path[len(sumdbPrefix)-1:]
Patrice Arruda748609c2020-06-25 12:12:21 -0700204 sumdbServer.ServeHTTP(w, r)
Colin Cross430342c2019-09-07 08:36:04 -0700205 return
206 }
207
208 // Module proxy request: /mod/path/@latest
209 // Rewrite to /mod/path/@v/<latest>.info where <latest> is the semantically
210 // latest version, including pseudo-versions.
211 if i := strings.LastIndex(path, "/@latest"); i >= 0 {
212 enc := path[:i]
Patrice Arruda748609c2020-06-25 12:12:21 -0700213 modPath, err := module.UnescapePath(enc)
Colin Cross430342c2019-09-07 08:36:04 -0700214 if err != nil {
Colin Cross1f805522021-05-14 11:10:59 -0700215 if testing.Verbose() {
Colin Cross430342c2019-09-07 08:36:04 -0700216 fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
217 }
218 http.NotFound(w, r)
219 return
220 }
221
222 // Imitate what "latest" does in direct mode and what proxy.golang.org does.
223 // Use the latest released version.
224 // If there is no released version, use the latest prereleased version.
225 // Otherwise, use the latest pseudoversion.
226 var latestRelease, latestPrerelease, latestPseudo string
227 for _, m := range modList {
228 if m.Path != modPath {
229 continue
230 }
Dan Willemsencc753b72021-08-31 13:25:42 -0700231 if module.IsPseudoVersion(m.Version) && (latestPseudo == "" || semver.Compare(latestPseudo, m.Version) > 0) {
Colin Cross430342c2019-09-07 08:36:04 -0700232 latestPseudo = m.Version
233 } else if semver.Prerelease(m.Version) != "" && (latestPrerelease == "" || semver.Compare(latestPrerelease, m.Version) > 0) {
234 latestPrerelease = m.Version
235 } else if latestRelease == "" || semver.Compare(latestRelease, m.Version) > 0 {
236 latestRelease = m.Version
237 }
238 }
239 var latest string
240 if latestRelease != "" {
241 latest = latestRelease
242 } else if latestPrerelease != "" {
243 latest = latestPrerelease
244 } else if latestPseudo != "" {
245 latest = latestPseudo
246 } else {
247 http.NotFound(w, r)
248 return
249 }
250
Patrice Arruda748609c2020-06-25 12:12:21 -0700251 encVers, err := module.EscapeVersion(latest)
Colin Cross430342c2019-09-07 08:36:04 -0700252 if err != nil {
253 http.Error(w, err.Error(), http.StatusInternalServerError)
254 return
255 }
256 path = fmt.Sprintf("%s/@v/%s.info", enc, encVers)
257 }
258
259 // Module proxy request: /mod/path/@v/version[.suffix]
Dan Willemsenc7413322018-08-27 23:21:26 -0700260 i := strings.Index(path, "/@v/")
261 if i < 0 {
262 http.NotFound(w, r)
263 return
264 }
265 enc, file := path[:i], path[i+len("/@v/"):]
Patrice Arruda748609c2020-06-25 12:12:21 -0700266 path, err := module.UnescapePath(enc)
Dan Willemsenc7413322018-08-27 23:21:26 -0700267 if err != nil {
Colin Cross1f805522021-05-14 11:10:59 -0700268 if testing.Verbose() {
Colin Cross430342c2019-09-07 08:36:04 -0700269 fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
270 }
Dan Willemsenc7413322018-08-27 23:21:26 -0700271 http.NotFound(w, r)
272 return
273 }
274 if file == "list" {
Colin Cross430342c2019-09-07 08:36:04 -0700275 // list returns a list of versions, not including pseudo-versions.
276 // If the module has no tagged versions, we should serve an empty 200.
277 // If the module doesn't exist, we should serve 404 or 410.
278 found := false
Dan Willemsenc7413322018-08-27 23:21:26 -0700279 for _, m := range modList {
Colin Cross430342c2019-09-07 08:36:04 -0700280 if m.Path != path {
281 continue
282 }
283 found = true
Dan Willemsencc753b72021-08-31 13:25:42 -0700284 if !module.IsPseudoVersion(m.Version) {
Dan Willemsenc7413322018-08-27 23:21:26 -0700285 if err := module.Check(m.Path, m.Version); err == nil {
286 fmt.Fprintf(w, "%s\n", m.Version)
Dan Willemsenc7413322018-08-27 23:21:26 -0700287 }
288 }
289 }
Colin Cross430342c2019-09-07 08:36:04 -0700290 if !found {
Dan Willemsenc7413322018-08-27 23:21:26 -0700291 http.NotFound(w, r)
292 }
293 return
294 }
295
296 i = strings.LastIndex(file, ".")
297 if i < 0 {
298 http.NotFound(w, r)
299 return
300 }
301 encVers, ext := file[:i], file[i+1:]
Patrice Arruda748609c2020-06-25 12:12:21 -0700302 vers, err := module.UnescapeVersion(encVers)
Dan Willemsenc7413322018-08-27 23:21:26 -0700303 if err != nil {
304 fmt.Fprintf(os.Stderr, "go proxy_test: %v\n", err)
305 http.NotFound(w, r)
306 return
307 }
308
309 if codehost.AllHex(vers) {
310 var best string
311 // Convert commit hash (only) to known version.
312 // Use latest version in semver priority, to match similar logic
313 // in the repo-based module server (see modfetch.(*codeRepo).convert).
314 for _, m := range modList {
315 if m.Path == path && semver.Compare(best, m.Version) < 0 {
316 var hash string
Dan Willemsencc753b72021-08-31 13:25:42 -0700317 if module.IsPseudoVersion(m.Version) {
Dan Willemsenc7413322018-08-27 23:21:26 -0700318 hash = m.Version[strings.LastIndex(m.Version, "-")+1:]
319 } else {
320 hash = findHash(m)
321 }
322 if strings.HasPrefix(hash, vers) || strings.HasPrefix(vers, hash) {
323 best = m.Version
324 }
325 }
326 }
327 if best != "" {
328 vers = best
329 }
330 }
331
Colin Cross430342c2019-09-07 08:36:04 -0700332 a, err := readArchive(path, vers)
333 if err != nil {
Colin Cross1f805522021-05-14 11:10:59 -0700334 if testing.Verbose() {
Colin Cross430342c2019-09-07 08:36:04 -0700335 fmt.Fprintf(os.Stderr, "go proxy: no archive %s %s: %v\n", path, vers, err)
336 }
Colin Cross1f805522021-05-14 11:10:59 -0700337 if errors.Is(err, fs.ErrNotExist) {
Colin Cross430342c2019-09-07 08:36:04 -0700338 http.NotFound(w, r)
339 } else {
340 http.Error(w, "cannot load archive", 500)
341 }
Dan Willemsenc7413322018-08-27 23:21:26 -0700342 return
343 }
344
345 switch ext {
346 case "info", "mod":
347 want := "." + ext
348 for _, f := range a.Files {
349 if f.Name == want {
350 w.Write(f.Data)
351 return
352 }
353 }
354
355 case "zip":
356 type cached struct {
357 zip []byte
358 err error
359 }
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800360 c := zipCache.Do(a, func() any {
Dan Willemsenc7413322018-08-27 23:21:26 -0700361 var buf bytes.Buffer
362 z := zip.NewWriter(&buf)
363 for _, f := range a.Files {
Dan Willemsencc753b72021-08-31 13:25:42 -0700364 if f.Name == ".info" || f.Name == ".mod" || f.Name == ".zip" {
Dan Willemsenc7413322018-08-27 23:21:26 -0700365 continue
366 }
Colin Crossd9c6b802019-03-19 21:10:31 -0700367 var zipName string
368 if strings.HasPrefix(f.Name, "/") {
369 zipName = f.Name[1:]
370 } else {
371 zipName = path + "@" + vers + "/" + f.Name
372 }
373 zf, err := z.Create(zipName)
Dan Willemsenc7413322018-08-27 23:21:26 -0700374 if err != nil {
375 return cached{nil, err}
376 }
377 if _, err := zf.Write(f.Data); err != nil {
378 return cached{nil, err}
379 }
380 }
381 if err := z.Close(); err != nil {
382 return cached{nil, err}
383 }
384 return cached{buf.Bytes(), nil}
385 }).(cached)
386
387 if c.err != nil {
Colin Cross1f805522021-05-14 11:10:59 -0700388 if testing.Verbose() {
Colin Cross430342c2019-09-07 08:36:04 -0700389 fmt.Fprintf(os.Stderr, "go proxy: %v\n", c.err)
390 }
Dan Willemsenc7413322018-08-27 23:21:26 -0700391 http.Error(w, c.err.Error(), 500)
392 return
393 }
394 w.Write(c.zip)
395 return
396
397 }
398 http.NotFound(w, r)
399}
400
401func findHash(m module.Version) string {
Colin Cross430342c2019-09-07 08:36:04 -0700402 a, err := readArchive(m.Path, m.Version)
403 if err != nil {
Dan Willemsenc7413322018-08-27 23:21:26 -0700404 return ""
405 }
406 var data []byte
407 for _, f := range a.Files {
408 if f.Name == ".info" {
409 data = f.Data
410 break
411 }
412 }
413 var info struct{ Short string }
414 json.Unmarshal(data, &info)
415 return info.Short
416}
417
418var archiveCache par.Cache
419
420var cmdGoDir, _ = os.Getwd()
421
Colin Cross430342c2019-09-07 08:36:04 -0700422func readArchive(path, vers string) (*txtar.Archive, error) {
Patrice Arruda748609c2020-06-25 12:12:21 -0700423 enc, err := module.EscapePath(path)
Dan Willemsenc7413322018-08-27 23:21:26 -0700424 if err != nil {
Colin Cross430342c2019-09-07 08:36:04 -0700425 return nil, err
Dan Willemsenc7413322018-08-27 23:21:26 -0700426 }
Patrice Arruda748609c2020-06-25 12:12:21 -0700427 encVers, err := module.EscapeVersion(vers)
Dan Willemsenc7413322018-08-27 23:21:26 -0700428 if err != nil {
Colin Cross430342c2019-09-07 08:36:04 -0700429 return nil, err
Dan Willemsenc7413322018-08-27 23:21:26 -0700430 }
431
Colin Crossd9c6b802019-03-19 21:10:31 -0700432 prefix := strings.ReplaceAll(enc, "/", "_")
Dan Willemsenc7413322018-08-27 23:21:26 -0700433 name := filepath.Join(cmdGoDir, "testdata/mod", prefix+"_"+encVers+".txt")
Dan Willemsenbc60c3c2021-12-15 01:09:00 -0800434 a := archiveCache.Do(name, func() any {
Dan Willemsenc7413322018-08-27 23:21:26 -0700435 a, err := txtar.ParseFile(name)
436 if err != nil {
437 if testing.Verbose() || !os.IsNotExist(err) {
438 fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
439 }
440 a = nil
441 }
442 return a
443 }).(*txtar.Archive)
Colin Cross430342c2019-09-07 08:36:04 -0700444 if a == nil {
Colin Cross1f805522021-05-14 11:10:59 -0700445 return nil, fs.ErrNotExist
Colin Cross430342c2019-09-07 08:36:04 -0700446 }
447 return a, nil
448}
449
450// proxyGoSum returns the two go.sum lines for path@vers.
451func proxyGoSum(path, vers string) ([]byte, error) {
452 a, err := readArchive(path, vers)
453 if err != nil {
454 return nil, err
455 }
456 var names []string
457 files := make(map[string][]byte)
458 var gomod []byte
459 for _, f := range a.Files {
460 if strings.HasPrefix(f.Name, ".") {
461 if f.Name == ".mod" {
462 gomod = f.Data
463 }
464 continue
465 }
466 name := path + "@" + vers + "/" + f.Name
467 names = append(names, name)
468 files[name] = f.Data
469 }
470 h1, err := dirhash.Hash1(names, func(name string) (io.ReadCloser, error) {
471 data := files[name]
Colin Cross1f805522021-05-14 11:10:59 -0700472 return io.NopCloser(bytes.NewReader(data)), nil
Colin Cross430342c2019-09-07 08:36:04 -0700473 })
474 if err != nil {
475 return nil, err
476 }
477 h1mod, err := dirhash.Hash1([]string{"go.mod"}, func(string) (io.ReadCloser, error) {
Colin Cross1f805522021-05-14 11:10:59 -0700478 return io.NopCloser(bytes.NewReader(gomod)), nil
Colin Cross430342c2019-09-07 08:36:04 -0700479 })
480 if err != nil {
481 return nil, err
482 }
483 data := []byte(fmt.Sprintf("%s %s %s\n%s %s/go.mod %s\n", path, vers, h1, path, vers, h1mod))
484 return data, nil
485}
486
487// proxyGoSumWrong returns the wrong lines.
488func proxyGoSumWrong(path, vers string) ([]byte, error) {
489 data := []byte(fmt.Sprintf("%s %s %s\n%s %s/go.mod %s\n", path, vers, "h1:wrong", path, vers, "h1:wrong"))
490 return data, nil
Dan Willemsenc7413322018-08-27 23:21:26 -0700491}