...

Source file src/golang.org/x/mod/gosumcheck/main.go

Documentation: golang.org/x/mod/gosumcheck

     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.
     4  
     5  // Gosumcheck checks a go.sum file against a go.sum database server.
     6  //
     7  // Usage:
     8  //
     9  //	gosumcheck [-h H] [-k key] [-u url] [-v] go.sum
    10  //
    11  // The -h flag changes the tile height (default 8).
    12  //
    13  // The -k flag changes the go.sum database server key.
    14  //
    15  // The -u flag overrides the URL of the server (usually set from the key name).
    16  //
    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.
    20  //
    21  // WARNING! WARNING! WARNING!
    22  //
    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.
    28  //
    29  // To discourage misuse in automated settings, gosumcheck does not
    30  // set any exit status to report whether any problems were found.
    31  package main
    32  
    33  import (
    34  	"flag"
    35  	"fmt"
    36  	"io"
    37  	"log"
    38  	"net/http"
    39  	"os"
    40  	"os/exec"
    41  	"strings"
    42  	"sync"
    43  	"time"
    44  
    45  	"golang.org/x/mod/sumdb"
    46  )
    47  
    48  func usage() {
    49  	fmt.Fprintf(os.Stderr, "usage: gosumcheck [-h H] [-k key] [-u url] [-v] go.sum...\n")
    50  	os.Exit(2)
    51  }
    52  
    53  var (
    54  	height = flag.Int("h", 8, "tile height")
    55  	vkey   = flag.String("k", "sum.golang.org+033de0ae+Ac4zctda0e5eza+HJyk9SxEdh+s3Ux18htTTAD8OuAn8", "key")
    56  	url    = flag.String("u", "", "url to server (overriding name)")
    57  	vflag  = flag.Bool("v", false, "enable verbose output")
    58  )
    59  
    60  func main() {
    61  	log.SetPrefix("notecheck: ")
    62  	log.SetFlags(0)
    63  
    64  	flag.Usage = usage
    65  	flag.Parse()
    66  	if flag.NArg() < 1 {
    67  		usage()
    68  	}
    69  
    70  	client := sumdb.NewClient(new(clientOps))
    71  
    72  	// Look in environment explicitly, so that if 'go env' is old and
    73  	// doesn't know about GONOSUMDB, we at least get anything
    74  	// set in the environment.
    75  	env := os.Getenv("GONOSUMDB")
    76  	if env == "" {
    77  		out, err := exec.Command("go", "env", "GONOSUMDB").CombinedOutput()
    78  		if err != nil {
    79  			log.Fatalf("go env GONOSUMDB: %v\n%s", err, out)
    80  		}
    81  		env = strings.TrimSpace(string(out))
    82  	}
    83  	client.SetGONOSUMDB(env)
    84  
    85  	for _, arg := range flag.Args() {
    86  		data, err := os.ReadFile(arg)
    87  		if err != nil {
    88  			log.Fatal(err)
    89  		}
    90  		checkGoSum(client, arg, data)
    91  	}
    92  }
    93  
    94  func checkGoSum(client *sumdb.Client, name string, data []byte) {
    95  	lines := strings.Split(string(data), "\n")
    96  	if lines[len(lines)-1] != "" {
    97  		log.Printf("error: final line missing newline")
    98  		return
    99  	}
   100  	lines = lines[:len(lines)-1]
   101  
   102  	errs := make([]string, len(lines))
   103  	var wg sync.WaitGroup
   104  	for i, line := range lines {
   105  		wg.Add(1)
   106  		go func(i int, line string) {
   107  			defer wg.Done()
   108  			f := strings.Fields(line)
   109  			if len(f) != 3 {
   110  				errs[i] = "invalid number of fields"
   111  				return
   112  			}
   113  
   114  			dbLines, err := client.Lookup(f[0], f[1])
   115  			if err != nil {
   116  				if err == sumdb.ErrGONOSUMDB {
   117  					errs[i] = fmt.Sprintf("%s@%s: %v", f[0], f[1], err)
   118  				} else {
   119  					// Otherwise Lookup properly adds the prefix itself.
   120  					errs[i] = err.Error()
   121  				}
   122  				return
   123  			}
   124  			hashAlgPrefix := f[0] + " " + f[1] + " " + f[2][:strings.Index(f[2], ":")+1]
   125  			for _, dbLine := range dbLines {
   126  				if dbLine == line {
   127  					return
   128  				}
   129  				if strings.HasPrefix(dbLine, hashAlgPrefix) {
   130  					errs[i] = fmt.Sprintf("%s@%s hash mismatch: have %s, want %s", f[0], f[1], line, dbLine)
   131  					return
   132  				}
   133  			}
   134  			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"))
   135  		}(i, line)
   136  	}
   137  	wg.Wait()
   138  
   139  	for i, err := range errs {
   140  		if err != "" {
   141  			fmt.Printf("%s:%d: %s\n", name, i+1, err)
   142  		}
   143  	}
   144  }
   145  
   146  type clientOps struct{}
   147  
   148  func (*clientOps) ReadConfig(file string) ([]byte, error) {
   149  	if file == "key" {
   150  		return []byte(*vkey), nil
   151  	}
   152  	if strings.HasSuffix(file, "/latest") {
   153  		// Looking for cached latest tree head.
   154  		// Empty result means empty tree.
   155  		return []byte{}, nil
   156  	}
   157  	return nil, fmt.Errorf("unknown config %s", file)
   158  }
   159  
   160  func (*clientOps) WriteConfig(file string, old, new []byte) error {
   161  	// Ignore writes.
   162  	return nil
   163  }
   164  
   165  func (*clientOps) ReadCache(file string) ([]byte, error) {
   166  	return nil, fmt.Errorf("no cache")
   167  }
   168  
   169  func (*clientOps) WriteCache(file string, data []byte) {
   170  	// Ignore writes.
   171  }
   172  
   173  func (*clientOps) Log(msg string) {
   174  	log.Print(msg)
   175  }
   176  
   177  func (*clientOps) SecurityError(msg string) {
   178  	log.Fatal(msg)
   179  }
   180  
   181  func init() {
   182  	http.DefaultClient.Timeout = 1 * time.Minute
   183  }
   184  
   185  func (*clientOps) ReadRemote(path string) ([]byte, error) {
   186  	name := *vkey
   187  	if i := strings.Index(name, "+"); i >= 0 {
   188  		name = name[:i]
   189  	}
   190  	start := time.Now()
   191  	target := "https://" + name + path
   192  	if *url != "" {
   193  		target = *url + path
   194  	}
   195  	resp, err := http.Get(target)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	defer resp.Body.Close()
   200  	if resp.StatusCode != 200 {
   201  		return nil, fmt.Errorf("GET %v: %v", target, resp.Status)
   202  	}
   203  	data, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
   204  	if err != nil {
   205  		return nil, err
   206  	}
   207  	if *vflag {
   208  		fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), target)
   209  	}
   210  	return data, nil
   211  }
   212  

View as plain text