1 // Copyright 2019 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.
5 // Gosumcheck checks a go.sum file against a go.sum database server.
9 // gosumcheck [-h H] [-k key] [-u url] [-v] go.sum
11 // The -h flag changes the tile height (default 8).
13 // The -k flag changes the go.sum database server key.
15 // The -u flag overrides the URL of the server (usually set from the key name).
17 // The -v flag enables verbose output.
18 // In particular, it causes gosumcheck to report
19 // the URL and elapsed time for each server request.
21 // WARNING! WARNING! WARNING!
23 // Gosumcheck is meant as a proof of concept demo and should not be
24 // used in production scripts or continuous integration testing.
25 // It does not cache any downloaded information from run to run,
26 // making it expensive and also keeping it from detecting server
27 // misbehavior or successful HTTPS man-in-the-middle timeline forks.
29 // To discourage misuse in automated settings, gosumcheck does not
30 // set any exit status to report whether any problems were found.
47 "golang.org/x/mod/sumdb"
51 fmt.Fprintf(os.Stderr, "usage: gosumcheck [-h H] [-k key] [-u url] [-v] go.sum...\n")
56 height = flag.Int("h", 8, "tile height")
57 vkey = flag.String("k", "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8", "key")
58 url = flag.String("u", "", "url to server (overriding name)")
59 vflag = flag.Bool("v", false, "enable verbose output")
63 log.SetPrefix("notecheck: ")
72 client := sumdb.NewClient(new(clientOps))
74 // Look in environment explicitly, so that if 'go env' is old and
75 // doesn't know about GONOSUMDB, we at least get anything
76 // set in the environment.
77 env := os.Getenv("GONOSUMDB")
79 out, err := exec.Command("go", "env", "GONOSUMDB").CombinedOutput()
81 log.Fatalf("go env GONOSUMDB: %v\n%s", err, out)
83 env = strings.TrimSpace(string(out))
85 client.SetGONOSUMDB(env)
87 for _, arg := range flag.Args() {
88 data, err := ioutil.ReadFile(arg)
92 checkGoSum(client, arg, data)
96 func checkGoSum(client *sumdb.Client, name string, data []byte) {
97 lines := strings.Split(string(data), "\n")
98 if lines[len(lines)-1] != "" {
99 log.Printf("error: final line missing newline")
102 lines = lines[:len(lines)-1]
104 errs := make([]string, len(lines))
105 var wg sync.WaitGroup
106 for i, line := range lines {
108 go func(i int, line string) {
110 f := strings.Fields(line)
112 errs[i] = "invalid number of fields"
116 dbLines, err := client.Lookup(f[0], f[1])
118 if err == sumdb.ErrGONOSUMDB {
119 errs[i] = fmt.Sprintf("%s@%s: %v", f[0], f[1], err)
121 // Otherwise Lookup properly adds the prefix itself.
122 errs[i] = err.Error()
126 hashAlgPrefix := f[0] + " " + f[1] + " " + f[2][:strings.Index(f[2], ":")+1]
127 for _, dbLine := range dbLines {
131 if strings.HasPrefix(dbLine, hashAlgPrefix) {
132 errs[i] = fmt.Sprintf("%s@%s hash mismatch: have %s, want %s", f[0], f[1], line, dbLine)
136 errs[i] = fmt.Sprintf("%s@%s hash algorithm mismatch: have %s, want one of:\n\t%s", f[0], f[1], line, strings.Join(dbLines, "\n\t"))
141 for i, err := range errs {
143 fmt.Printf("%s:%d: %s\n", name, i+1, err)
148 type clientOps struct{}
150 func (*clientOps) ReadConfig(file string) ([]byte, error) {
152 return []byte(*vkey), nil
154 if strings.HasSuffix(file, "/latest") {
155 // Looking for cached latest tree head.
156 // Empty result means empty tree.
159 return nil, fmt.Errorf("unknown config %s", file)
162 func (*clientOps) WriteConfig(file string, old, new []byte) error {
167 func (*clientOps) ReadCache(file string) ([]byte, error) {
168 return nil, fmt.Errorf("no cache")
171 func (*clientOps) WriteCache(file string, data []byte) {
175 func (*clientOps) Log(msg string) {
179 func (*clientOps) SecurityError(msg string) {
184 http.DefaultClient.Timeout = 1 * time.Minute
187 func (*clientOps) ReadRemote(path string) ([]byte, error) {
189 if i := strings.Index(name, "+"); i >= 0 {
193 target := "https://" + name + path
197 resp, err := http.Get(target)
201 defer resp.Body.Close()
202 if resp.StatusCode != 200 {
203 return nil, fmt.Errorf("GET %v: %v", target, resp.Status)
205 data, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
210 fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), target)