github.com/zxy12/go_duplicate_112_new@v0.0.0-20200807091221-747231827200/src/cmd/go/internal/modfetch/proxy.go (about)

     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  
     5  package modfetch
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"io"
    11  	"net/url"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"cmd/go/internal/base"
    17  	"cmd/go/internal/modfetch/codehost"
    18  	"cmd/go/internal/module"
    19  	"cmd/go/internal/semver"
    20  )
    21  
    22  var HelpGoproxy = &base.Command{
    23  	UsageLine: "goproxy",
    24  	Short:     "module proxy protocol",
    25  	Long: `
    26  The go command by default downloads modules from version control systems
    27  directly, just as 'go get' always has. The GOPROXY environment variable allows
    28  further control over the download source. If GOPROXY is unset, is the empty string,
    29  or is the string "direct", downloads use the default direct connection to version
    30  control systems. Setting GOPROXY to "off" disallows downloading modules from
    31  any source. Otherwise, GOPROXY is expected to be the URL of a module proxy,
    32  in which case the go command will fetch all modules from that proxy.
    33  No matter the source of the modules, downloaded modules must match existing
    34  entries in go.sum (see 'go help modules' for discussion of verification).
    35  
    36  A Go module proxy is any web server that can respond to GET requests for
    37  URLs of a specified form. The requests have no query parameters, so even
    38  a site serving from a fixed file system (including a file:/// URL)
    39  can be a module proxy.
    40  
    41  The GET requests sent to a Go module proxy are:
    42  
    43  GET $GOPROXY/<module>/@v/list returns a list of all known versions of the
    44  given module, one per line.
    45  
    46  GET $GOPROXY/<module>/@v/<version>.info returns JSON-formatted metadata
    47  about that version of the given module.
    48  
    49  GET $GOPROXY/<module>/@v/<version>.mod returns the go.mod file
    50  for that version of the given module.
    51  
    52  GET $GOPROXY/<module>/@v/<version>.zip returns the zip archive
    53  for that version of the given module.
    54  
    55  To avoid problems when serving from case-sensitive file systems,
    56  the <module> and <version> elements are case-encoded, replacing every
    57  uppercase letter with an exclamation mark followed by the corresponding
    58  lower-case letter: github.com/Azure encodes as github.com/!azure.
    59  
    60  The JSON-formatted metadata about a given module corresponds to
    61  this Go data structure, which may be expanded in the future:
    62  
    63      type Info struct {
    64          Version string    // version string
    65          Time    time.Time // commit time
    66      }
    67  
    68  The zip archive for a specific version of a given module is a
    69  standard zip file that contains the file tree corresponding
    70  to the module's source code and related files. The archive uses
    71  slash-separated paths, and every file path in the archive must
    72  begin with <module>@<version>/, where the module and version are
    73  substituted directly, not case-encoded. The root of the module
    74  file tree corresponds to the <module>@<version>/ prefix in the
    75  archive.
    76  
    77  Even when downloading directly from version control systems,
    78  the go command synthesizes explicit info, mod, and zip files
    79  and stores them in its local cache, $GOPATH/pkg/mod/cache/download,
    80  the same as if it had downloaded them directly from a proxy.
    81  The cache layout is the same as the proxy URL space, so
    82  serving $GOPATH/pkg/mod/cache/download at (or copying it to)
    83  https://example.com/proxy would let other users access those
    84  cached module versions with GOPROXY=https://example.com/proxy.
    85  `,
    86  }
    87  
    88  var proxyURL = os.Getenv("GOPROXY")
    89  
    90  func lookupProxy(path string) (Repo, error) {
    91  	if strings.Contains(proxyURL, ",") {
    92  		return nil, fmt.Errorf("invalid $GOPROXY setting: cannot have comma")
    93  	}
    94  	u, err := url.Parse(proxyURL)
    95  	if err != nil || u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "file" {
    96  		// Don't echo $GOPROXY back in case it has user:password in it (sigh).
    97  		return nil, fmt.Errorf("invalid $GOPROXY setting: malformed URL or invalid scheme (must be http, https, file)")
    98  	}
    99  	return newProxyRepo(u.String(), path)
   100  }
   101  
   102  type proxyRepo struct {
   103  	url  string
   104  	path string
   105  }
   106  
   107  func newProxyRepo(baseURL, path string) (Repo, error) {
   108  	enc, err := module.EncodePath(path)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  	return &proxyRepo{strings.TrimSuffix(baseURL, "/") + "/" + pathEscape(enc), path}, nil
   113  }
   114  
   115  func (p *proxyRepo) ModulePath() string {
   116  	return p.path
   117  }
   118  
   119  func (p *proxyRepo) Versions(prefix string) ([]string, error) {
   120  	var data []byte
   121  	err := webGetBytes(p.url+"/@v/list", &data)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	var list []string
   126  	for _, line := range strings.Split(string(data), "\n") {
   127  		f := strings.Fields(line)
   128  		if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) {
   129  			list = append(list, f[0])
   130  		}
   131  	}
   132  	SortVersions(list)
   133  	return list, nil
   134  }
   135  
   136  func (p *proxyRepo) latest() (*RevInfo, error) {
   137  	var data []byte
   138  	err := webGetBytes(p.url+"/@v/list", &data)
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	var best time.Time
   143  	var bestVersion string
   144  	for _, line := range strings.Split(string(data), "\n") {
   145  		f := strings.Fields(line)
   146  		if len(f) >= 2 && semver.IsValid(f[0]) {
   147  			ft, err := time.Parse(time.RFC3339, f[1])
   148  			if err == nil && best.Before(ft) {
   149  				best = ft
   150  				bestVersion = f[0]
   151  			}
   152  		}
   153  	}
   154  	if bestVersion == "" {
   155  		return nil, fmt.Errorf("no commits")
   156  	}
   157  	info := &RevInfo{
   158  		Version: bestVersion,
   159  		Name:    bestVersion,
   160  		Short:   bestVersion,
   161  		Time:    best,
   162  	}
   163  	return info, nil
   164  }
   165  
   166  func (p *proxyRepo) Stat(rev string) (*RevInfo, error) {
   167  	var data []byte
   168  	encRev, err := module.EncodeVersion(rev)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	err = webGetBytes(p.url+"/@v/"+pathEscape(encRev)+".info", &data)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	info := new(RevInfo)
   177  	if err := json.Unmarshal(data, info); err != nil {
   178  		return nil, err
   179  	}
   180  	return info, nil
   181  }
   182  
   183  func (p *proxyRepo) Latest() (*RevInfo, error) {
   184  	var data []byte
   185  	u := p.url + "/@latest"
   186  	err := webGetBytes(u, &data)
   187  	if err != nil {
   188  		// TODO return err if not 404
   189  		return p.latest()
   190  	}
   191  	info := new(RevInfo)
   192  	if err := json.Unmarshal(data, info); err != nil {
   193  		return nil, err
   194  	}
   195  	return info, nil
   196  }
   197  
   198  func (p *proxyRepo) GoMod(version string) ([]byte, error) {
   199  	var data []byte
   200  	encVer, err := module.EncodeVersion(version)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	err = webGetBytes(p.url+"/@v/"+pathEscape(encVer)+".mod", &data)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	return data, nil
   209  }
   210  
   211  func (p *proxyRepo) Zip(dst io.Writer, version string) error {
   212  	var body io.ReadCloser
   213  	encVer, err := module.EncodeVersion(version)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	err = webGetBody(p.url+"/@v/"+pathEscape(encVer)+".zip", &body)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	defer body.Close()
   222  
   223  	lr := &io.LimitedReader{R: body, N: codehost.MaxZipFile + 1}
   224  	if _, err := io.Copy(dst, lr); err != nil {
   225  		return err
   226  	}
   227  	if lr.N <= 0 {
   228  		return fmt.Errorf("downloaded zip file too large")
   229  	}
   230  	return nil
   231  }
   232  
   233  // pathEscape escapes s so it can be used in a path.
   234  // That is, it escapes things like ? and # (which really shouldn't appear anyway).
   235  // It does not escape / to %2F: our REST API is designed so that / can be left as is.
   236  func pathEscape(s string) string {
   237  	return strings.ReplaceAll(url.PathEscape(s), "%2F", "/")
   238  }