github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/modfetch/sumdb.go (about)

     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  // Go checksum database lookup
     6  
     7  // +build !cmd_go_bootstrap
     8  
     9  package modfetch
    10  
    11  import (
    12  	"bytes"
    13  	"errors"
    14  	"fmt"
    15  	"io/ioutil"
    16  	"net/url"
    17  	"os"
    18  	"path/filepath"
    19  	"strings"
    20  	"sync"
    21  	"time"
    22  
    23  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/base"
    24  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/cfg"
    25  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/get"
    26  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/lockedfile"
    27  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/str"
    28  	"github.com/gagliardetto/golang-go/cmd/go/not-internal/web"
    29  	"golang.org/x/mod/module"
    30  	"golang.org/x/mod/sumdb"
    31  	"golang.org/x/mod/sumdb/note"
    32  )
    33  
    34  // useSumDB reports whether to use the Go checksum database for the given module.
    35  func useSumDB(mod module.Version) bool {
    36  	return cfg.GOSUMDB != "off" && !get.Insecure && !str.GlobsMatchPath(cfg.GONOSUMDB, mod.Path)
    37  }
    38  
    39  // lookupSumDB returns the Go checksum database's go.sum lines for the given module,
    40  // along with the name of the database.
    41  func lookupSumDB(mod module.Version) (dbname string, lines []string, err error) {
    42  	dbOnce.Do(func() {
    43  		dbName, db, dbErr = dbDial()
    44  	})
    45  	if dbErr != nil {
    46  		return "", nil, dbErr
    47  	}
    48  	lines, err = db.Lookup(mod.Path, mod.Version)
    49  	return dbName, lines, err
    50  }
    51  
    52  var (
    53  	dbOnce sync.Once
    54  	dbName string
    55  	db     *sumdb.Client
    56  	dbErr  error
    57  )
    58  
    59  func dbDial() (dbName string, db *sumdb.Client, err error) {
    60  	// $GOSUMDB can be "key" or "key url",
    61  	// and the key can be a full verifier key
    62  	// or a host on our list of known keys.
    63  
    64  	// Special case: sum.golang.google.cn
    65  	// is an alias, reachable inside mainland China,
    66  	// for sum.golang.org. If there are more
    67  	// of these we should add a map like knownGOSUMDB.
    68  	gosumdb := cfg.GOSUMDB
    69  	if gosumdb == "sum.golang.google.cn" {
    70  		gosumdb = "sum.golang.org https://sum.golang.google.cn"
    71  	}
    72  
    73  	key := strings.Fields(gosumdb)
    74  	if len(key) >= 1 {
    75  		if k := knownGOSUMDB[key[0]]; k != "" {
    76  			key[0] = k
    77  		}
    78  	}
    79  	if len(key) == 0 {
    80  		return "", nil, fmt.Errorf("missing GOSUMDB")
    81  	}
    82  	if len(key) > 2 {
    83  		return "", nil, fmt.Errorf("invalid GOSUMDB: too many fields")
    84  	}
    85  	vkey, err := note.NewVerifier(key[0])
    86  	if err != nil {
    87  		return "", nil, fmt.Errorf("invalid GOSUMDB: %v", err)
    88  	}
    89  	name := vkey.Name()
    90  
    91  	// No funny business in the database name.
    92  	direct, err := url.Parse("https://" + name)
    93  	if err != nil || strings.HasSuffix(name, "/") || *direct != (url.URL{Scheme: "https", Host: direct.Host, Path: direct.Path, RawPath: direct.RawPath}) || direct.RawPath != "" || direct.Host == "" {
    94  		return "", nil, fmt.Errorf("invalid sumdb name (must be host[/path]): %s %+v", name, *direct)
    95  	}
    96  
    97  	// Determine how to get to database.
    98  	var base *url.URL
    99  	if len(key) >= 2 {
   100  		// Use explicit alternate URL listed in $GOSUMDB,
   101  		// bypassing both the default URL derivation and any proxies.
   102  		u, err := url.Parse(key[1])
   103  		if err != nil {
   104  			return "", nil, fmt.Errorf("invalid GOSUMDB URL: %v", err)
   105  		}
   106  		base = u
   107  	}
   108  
   109  	return name, sumdb.NewClient(&dbClient{key: key[0], name: name, direct: direct, base: base}), nil
   110  }
   111  
   112  type dbClient struct {
   113  	key    string
   114  	name   string
   115  	direct *url.URL
   116  
   117  	once    sync.Once
   118  	base    *url.URL
   119  	baseErr error
   120  }
   121  
   122  func (c *dbClient) ReadRemote(path string) ([]byte, error) {
   123  	c.once.Do(c.initBase)
   124  	if c.baseErr != nil {
   125  		return nil, c.baseErr
   126  	}
   127  
   128  	var data []byte
   129  	start := time.Now()
   130  	targ := web.Join(c.base, path)
   131  	data, err := web.GetBytes(targ)
   132  	if false {
   133  		fmt.Fprintf(os.Stderr, "%.3fs %s\n", time.Since(start).Seconds(), web.Redacted(targ))
   134  	}
   135  	return data, err
   136  }
   137  
   138  // initBase determines the base URL for connecting to the database.
   139  // Determining the URL requires sending network traffic to proxies,
   140  // so this work is delayed until we need to download something from
   141  // the database. If everything we need is in the local cache and
   142  // c.ReadRemote is never called, we will never do this work.
   143  func (c *dbClient) initBase() {
   144  	if c.base != nil {
   145  		return
   146  	}
   147  
   148  	// Try proxies in turn until we find out how to connect to this database.
   149  	urls, err := proxyURLs()
   150  	if err != nil {
   151  		c.baseErr = err
   152  		return
   153  	}
   154  	for _, proxyURL := range urls {
   155  		if proxyURL == "noproxy" {
   156  			continue
   157  		}
   158  		if proxyURL == "direct" || proxyURL == "off" {
   159  			break
   160  		}
   161  		proxy, err := url.Parse(proxyURL)
   162  		if err != nil {
   163  			c.baseErr = err
   164  			return
   165  		}
   166  		// Quoting https://golang.org/design/25530-sumdb#proxying-a-checksum-database:
   167  		//
   168  		// Before accessing any checksum database URL using a proxy,
   169  		// the proxy client should first fetch <proxyURL>/sumdb/<sumdb-name>/supported.
   170  		// If that request returns a successful (HTTP 200) response, then the proxy supports
   171  		// proxying checksum database requests. In that case, the client should use
   172  		// the proxied access method only, never falling back to a direct connection to the database.
   173  		// If the /sumdb/<sumdb-name>/supported check fails with a “not found” (HTTP 404)
   174  		// or “gone” (HTTP 410) response, the proxy is unwilling to proxy the checksum database,
   175  		// and the client should connect directly to the database.
   176  		// Any other response is treated as the database being unavailable.
   177  		_, err = web.GetBytes(web.Join(proxy, "sumdb/"+c.name+"/supported"))
   178  		if err == nil {
   179  			// Success! This proxy will help us.
   180  			c.base = web.Join(proxy, "sumdb/"+c.name)
   181  			return
   182  		}
   183  		// If the proxy serves a non-404/410, give up.
   184  		if !errors.Is(err, os.ErrNotExist) {
   185  			c.baseErr = err
   186  			return
   187  		}
   188  	}
   189  
   190  	// No proxies, or all proxies said 404, or we reached an explicit "direct".
   191  	c.base = c.direct
   192  }
   193  
   194  // ReadConfig reads the key from c.key
   195  // and otherwise reads the config (a latest tree head) from GOPATH/pkg/sumdb/<file>.
   196  func (c *dbClient) ReadConfig(file string) (data []byte, err error) {
   197  	if file == "key" {
   198  		return []byte(c.key), nil
   199  	}
   200  
   201  	// GOPATH/pkg is PkgMod/..
   202  	targ := filepath.Join(PkgMod, "../sumdb/"+file)
   203  	data, err = lockedfile.Read(targ)
   204  	if errors.Is(err, os.ErrNotExist) {
   205  		// Treat non-existent as empty, to bootstrap the "latest" file
   206  		// the first time we connect to a given database.
   207  		return []byte{}, nil
   208  	}
   209  	return data, err
   210  }
   211  
   212  // WriteConfig rewrites the latest tree head.
   213  func (*dbClient) WriteConfig(file string, old, new []byte) error {
   214  	if file == "key" {
   215  		// Should not happen.
   216  		return fmt.Errorf("cannot write key")
   217  	}
   218  	targ := filepath.Join(PkgMod, "../sumdb/"+file)
   219  	os.MkdirAll(filepath.Dir(targ), 0777)
   220  	f, err := lockedfile.Edit(targ)
   221  	if err != nil {
   222  		return err
   223  	}
   224  	defer f.Close()
   225  	data, err := ioutil.ReadAll(f)
   226  	if err != nil {
   227  		return err
   228  	}
   229  	if len(data) > 0 && !bytes.Equal(data, old) {
   230  		return sumdb.ErrWriteConflict
   231  	}
   232  	if _, err := f.Seek(0, 0); err != nil {
   233  		return err
   234  	}
   235  	if err := f.Truncate(0); err != nil {
   236  		return err
   237  	}
   238  	if _, err := f.Write(new); err != nil {
   239  		return err
   240  	}
   241  	return f.Close()
   242  }
   243  
   244  // ReadCache reads cached lookups or tiles from
   245  // GOPATH/pkg/mod/cache/download/sumdb,
   246  // which will be deleted by "go clean -modcache".
   247  func (*dbClient) ReadCache(file string) ([]byte, error) {
   248  	targ := filepath.Join(PkgMod, "cache/download/sumdb", file)
   249  	data, err := lockedfile.Read(targ)
   250  	// lockedfile.Write does not atomically create the file with contents.
   251  	// There is a moment between file creation and locking the file for writing,
   252  	// during which the empty file can be locked for reading.
   253  	// Treat observing an empty file as file not found.
   254  	if err == nil && len(data) == 0 {
   255  		err = &os.PathError{Op: "read", Path: targ, Err: os.ErrNotExist}
   256  	}
   257  	return data, err
   258  }
   259  
   260  // WriteCache updates cached lookups or tiles.
   261  func (*dbClient) WriteCache(file string, data []byte) {
   262  	targ := filepath.Join(PkgMod, "cache/download/sumdb", file)
   263  	os.MkdirAll(filepath.Dir(targ), 0777)
   264  	lockedfile.Write(targ, bytes.NewReader(data), 0666)
   265  }
   266  
   267  func (*dbClient) Log(msg string) {
   268  	// nothing for now
   269  }
   270  
   271  func (*dbClient) SecurityError(msg string) {
   272  	base.Fatalf("%s", msg)
   273  }