blob: 419323bb3bb8b5d22cf805365627da7820b25be7 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package modfetch
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"strings"
"time"
"cmd/go/internal/modfetch/codehost"
"cmd/go/internal/semver"
)
var proxyURL = os.Getenv("GOPROXY")
func lookupProxy(path string) (Repo, error) {
u, err := url.Parse(proxyURL)
if err != nil || u.Scheme != "http" && u.Scheme != "https" && u.Scheme != "file" {
// Don't echo $GOPROXY back in case it has user:password in it (sigh).
return nil, fmt.Errorf("invalid $GOPROXY setting")
}
return newProxyRepo(u.String(), path), nil
}
type proxyRepo struct {
url string
path string
}
func newProxyRepo(baseURL, path string) Repo {
return &proxyRepo{strings.TrimSuffix(baseURL, "/") + "/" + pathEscape(path), path}
}
func (p *proxyRepo) ModulePath() string {
return p.path
}
func (p *proxyRepo) Versions(prefix string) ([]string, error) {
var data []byte
err := webGetBytes(p.url+"/@v/list", &data)
if err != nil {
return nil, err
}
var list []string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) >= 1 && semver.IsValid(f[0]) && strings.HasPrefix(f[0], prefix) {
list = append(list, f[0])
}
}
return list, nil
}
func (p *proxyRepo) latest() (*RevInfo, error) {
var data []byte
err := webGetBytes(p.url+"/@v/list", &data)
if err != nil {
return nil, err
}
var best time.Time
var bestVersion string
for _, line := range strings.Split(string(data), "\n") {
f := strings.Fields(line)
if len(f) >= 2 && semver.IsValid(f[0]) {
ft, err := time.Parse(time.RFC3339, f[1])
if err == nil && best.Before(ft) {
best = ft
bestVersion = f[0]
}
}
}
if bestVersion == "" {
return nil, fmt.Errorf("no commits")
}
info := &RevInfo{
Version: bestVersion,
Name: bestVersion,
Short: bestVersion,
Time: best,
}
return info, nil
}
func (p *proxyRepo) Stat(rev string) (*RevInfo, error) {
var data []byte
err := webGetBytes(p.url+"/@v/"+pathEscape(rev)+".info", &data)
if err != nil {
return nil, err
}
info := new(RevInfo)
if err := json.Unmarshal(data, info); err != nil {
return nil, err
}
return info, nil
}
func (p *proxyRepo) Latest() (*RevInfo, error) {
var data []byte
u := p.url + "/@latest"
err := webGetBytes(u, &data)
if err != nil {
// TODO return err if not 404
return p.latest()
}
info := new(RevInfo)
if err := json.Unmarshal(data, info); err != nil {
return nil, err
}
return info, nil
}
func (p *proxyRepo) GoMod(version string) ([]byte, error) {
var data []byte
err := webGetBytes(p.url+"/@v/"+pathEscape(version)+".mod", &data)
if err != nil {
return nil, err
}
return data, nil
}
func (p *proxyRepo) Zip(version string, tmpdir string) (tmpfile string, err error) {
var body io.ReadCloser
err = webGetBody(p.url+"/@v/"+pathEscape(version)+".zip", &body)
if err != nil {
return "", err
}
defer body.Close()
// Spool to local file.
f, err := ioutil.TempFile(tmpdir, "vgo-proxy-download-")
if err != nil {
return "", err
}
defer f.Close()
maxSize := int64(codehost.MaxZipFile)
lr := &io.LimitedReader{R: body, N: maxSize + 1}
if _, err := io.Copy(f, lr); err != nil {
os.Remove(f.Name())
return "", err
}
if lr.N <= 0 {
os.Remove(f.Name())
return "", fmt.Errorf("downloaded zip file too large")
}
if err := f.Close(); err != nil {
os.Remove(f.Name())
return "", err
}
return f.Name(), nil
}
// pathEscape escapes s so it can be used in a path.
// That is, it escapes things like ? and # (which really shouldn't appear anyway).
// It does not escape / to %2F: our REST API is designed so that / can be left as is.
func pathEscape(s string) string {
return strings.Replace(url.PathEscape(s), "%2F", "/", -1)
}