Commit 5fa14a31 authored by Russ Cox's avatar Russ Cox

cmd/go/internal/web: minor api cleanup

- rename PasswordRedacted to Redacted
- move URL into Response in redacted form, remove from Get result list
- add Response.Err to construct non-200 errors
  (otherwise GetBytes is not just a wrapper)
- make 404/410 errors satisfy Is(err, os.ErrNotExist)

Change-Id: Id15899c1e3dfd30cffb1a75ba79a9a1999913258
Reviewed-on: https://go-review.googlesource.com/c/go/+/173717
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: default avatarBryan C. Mills <bcmills@google.com>
parent be857a63
...@@ -782,7 +782,7 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se ...@@ -782,7 +782,7 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
if err != nil { if err != nil {
return nil, err return nil, err
} }
url, resp, err := web.Get(security, url) resp, err := web.Get(security, url)
if err != nil { if err != nil {
msg := "https fetch: %v" msg := "https fetch: %v"
if security == web.Insecure { if security == web.Insecure {
...@@ -802,7 +802,7 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se ...@@ -802,7 +802,7 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
if _, ok := err.(ImportMismatchError); !ok { if _, ok := err.(ImportMismatchError); !ok {
return nil, fmt.Errorf("parse %s: %v", url, err) return nil, fmt.Errorf("parse %s: %v", url, err)
} }
return nil, fmt.Errorf("parse %s: no go-import meta tags (%s)", url, err) return nil, fmt.Errorf("parse %s: no go-import meta tags (%s)", resp.URL, err)
} }
if cfg.BuildV { if cfg.BuildV {
log.Printf("get %q: found meta tag %#v at %s", importPath, mmi, url) log.Printf("get %q: found meta tag %#v at %s", importPath, mmi, url)
...@@ -817,7 +817,6 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se ...@@ -817,7 +817,6 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
if cfg.BuildV { if cfg.BuildV {
log.Printf("get %q: verifying non-authoritative meta tag", importPath) log.Printf("get %q: verifying non-authoritative meta tag", importPath)
} }
url0 := *url
var imports []metaImport var imports []metaImport
url, imports, err = metaImportsForPrefix(mmi.Prefix, mod, security) url, imports, err = metaImportsForPrefix(mmi.Prefix, mod, security)
if err != nil { if err != nil {
...@@ -825,16 +824,16 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se ...@@ -825,16 +824,16 @@ func repoRootForImportDynamic(importPath string, mod ModuleMode, security web.Se
} }
metaImport2, err := matchGoImport(imports, importPath) metaImport2, err := matchGoImport(imports, importPath)
if err != nil || mmi != metaImport2 { if err != nil || mmi != metaImport2 {
return nil, fmt.Errorf("%s and %s disagree about go-import for %s", &url0, url, mmi.Prefix) return nil, fmt.Errorf("%s and %s disagree about go-import for %s", resp.URL, url, mmi.Prefix)
} }
} }
if err := validateRepoRoot(mmi.RepoRoot); err != nil { if err := validateRepoRoot(mmi.RepoRoot); err != nil {
return nil, fmt.Errorf("%s: invalid repo root %q: %v", url, mmi.RepoRoot, err) return nil, fmt.Errorf("%s: invalid repo root %q: %v", resp.URL, mmi.RepoRoot, err)
} }
vcs := vcsByCmd(mmi.VCS) vcs := vcsByCmd(mmi.VCS)
if vcs == nil && mmi.VCS != "mod" { if vcs == nil && mmi.VCS != "mod" {
return nil, fmt.Errorf("%s: unknown vcs %q", url, mmi.VCS) return nil, fmt.Errorf("%s: unknown vcs %q", resp.URL, mmi.VCS)
} }
rr := &RepoRoot{ rr := &RepoRoot{
...@@ -894,15 +893,15 @@ func metaImportsForPrefix(importPrefix string, mod ModuleMode, security web.Secu ...@@ -894,15 +893,15 @@ func metaImportsForPrefix(importPrefix string, mod ModuleMode, security web.Secu
if err != nil { if err != nil {
return setCache(fetchResult{err: err}) return setCache(fetchResult{err: err})
} }
url, resp, err := web.Get(security, url) resp, err := web.Get(security, url)
if err != nil { if err != nil {
return setCache(fetchResult{url: url, err: fmt.Errorf("fetch %s: %v", url, err)}) return setCache(fetchResult{url: url, err: fmt.Errorf("fetch %s: %v", resp.URL, err)})
} }
body := resp.Body body := resp.Body
defer body.Close() defer body.Close()
imports, err := parseMetaGoImports(body, mod) imports, err := parseMetaGoImports(body, mod)
if err != nil { if err != nil {
return setCache(fetchResult{url: url, err: fmt.Errorf("parsing %s: %v", url, err)}) return setCache(fetchResult{url: url, err: fmt.Errorf("parsing %s: %v", resp.URL, err)})
} }
if len(imports) == 0 { if len(imports) == 0 {
err = fmt.Errorf("fetch %s: no go-import meta tag", url) err = fmt.Errorf("fetch %s: no go-import meta tag", url)
......
...@@ -124,11 +124,11 @@ func newProxyRepo(baseURL, path string) (Repo, error) { ...@@ -124,11 +124,11 @@ func newProxyRepo(baseURL, path string) (Repo, error) {
switch url.Scheme { switch url.Scheme {
case "file": case "file":
if *url != (urlpkg.URL{Scheme: url.Scheme, Path: url.Path, RawPath: url.RawPath}) { if *url != (urlpkg.URL{Scheme: url.Scheme, Path: url.Path, RawPath: url.RawPath}) {
return nil, fmt.Errorf("proxy URL %q uses file scheme with non-path elements", web.PasswordRedacted(url)) return nil, fmt.Errorf("proxy URL %q uses file scheme with non-path elements", web.Redacted(url))
} }
case "http", "https": case "http", "https":
case "": case "":
return nil, fmt.Errorf("proxy URL %q missing scheme", web.PasswordRedacted(url)) return nil, fmt.Errorf("proxy URL %q missing scheme", web.Redacted(url))
default: default:
return nil, fmt.Errorf("unsupported proxy scheme %q", url.Scheme) return nil, fmt.Errorf("unsupported proxy scheme %q", url.Scheme)
} }
...@@ -171,12 +171,12 @@ func (p *proxyRepo) getBody(path string) (io.ReadCloser, error) { ...@@ -171,12 +171,12 @@ func (p *proxyRepo) getBody(path string) (io.ReadCloser, error) {
url.Path = fullPath url.Path = fullPath
url.RawPath = pathpkg.Join(url.RawPath, pathEscape(path)) url.RawPath = pathpkg.Join(url.RawPath, pathEscape(path))
_, resp, err := web.Get(web.DefaultSecurity, url) resp, err := web.Get(web.DefaultSecurity, url)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
return nil, fmt.Errorf("unexpected status (%s): %v", web.PasswordRedacted(url), resp.Status) return nil, fmt.Errorf("unexpected status (%s): %v", web.Redacted(url), resp.Status)
} }
return resp.Body, nil return resp.Body, nil
} }
......
...@@ -13,7 +13,8 @@ import ( ...@@ -13,7 +13,8 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
urlpkg "net/url" "net/url"
"os"
) )
// SecurityMode specifies whether a function should make network // SecurityMode specifies whether a function should make network
...@@ -27,50 +28,65 @@ const ( ...@@ -27,50 +28,65 @@ const (
Insecure // Allow plain HTTP if not explicitly HTTPS; skip HTTPS validation. Insecure // Allow plain HTTP if not explicitly HTTPS; skip HTTPS validation.
) )
// An HTTPError describes an HTTP error response (non-200 result).
type HTTPError struct { type HTTPError struct {
status string URL string // redacted
Status string
StatusCode int StatusCode int
url *urlpkg.URL
} }
func (e *HTTPError) Error() string { func (e *HTTPError) Error() string {
return fmt.Sprintf("%s: %s", e.url, e.status) return fmt.Sprintf("reading %s: %v", e.URL, e.Status)
}
func (e *HTTPError) Is(target error) bool {
return target == os.ErrNotExist && (e.StatusCode == 404 || e.StatusCode == 410)
} }
// GetBytes returns the body of the requested resource, or an error if the // GetBytes returns the body of the requested resource, or an error if the
// response status was not http.StatusOk. // response status was not http.StatusOK.
// //
// GetBytes is a convenience wrapper around Get. // GetBytes is a convenience wrapper around Get and Response.Err.
func GetBytes(url *urlpkg.URL) ([]byte, error) { func GetBytes(u *url.URL) ([]byte, error) {
url, resp, err := Get(DefaultSecurity, url) resp, err := Get(DefaultSecurity, u)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if err := resp.Err(); err != nil {
err := &HTTPError{status: resp.Status, StatusCode: resp.StatusCode, url: url}
return nil, err return nil, err
} }
b, err := ioutil.ReadAll(resp.Body) b, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %v", url, err) return nil, fmt.Errorf("reading %s: %v", Redacted(u), err)
} }
return b, nil return b, nil
} }
type Response struct { type Response struct {
URL string // redacted
Status string Status string
StatusCode int StatusCode int
Header map[string][]string Header map[string][]string
Body io.ReadCloser Body io.ReadCloser
} }
// Err returns an *HTTPError corresponding to the response r.
// It returns nil if the response r has StatusCode 200 or 0 (unset).
func (r *Response) Err() error {
if r.StatusCode == 200 || r.StatusCode == 0 {
return nil
}
return &HTTPError{URL: r.URL, Status: r.Status, StatusCode: r.StatusCode}
}
// Get returns the body of the HTTP or HTTPS resource specified at the given URL. // Get returns the body of the HTTP or HTTPS resource specified at the given URL.
// //
// If the URL does not include an explicit scheme, Get first tries "https". // If the URL does not include an explicit scheme, Get first tries "https".
// If the server does not respond under that scheme and the security mode is // If the server does not respond under that scheme and the security mode is
// Insecure, Get then tries "http". // Insecure, Get then tries "http".
// The returned URL indicates which scheme was actually used. // The URL included in the response indicates which scheme was actually used,
// and it is a redacted URL suitable for use in error messages.
// //
// For the "https" scheme only, credentials are attached using the // For the "https" scheme only, credentials are attached using the
// cmd/go/internal/auth package. If the URL itself includes a username and // cmd/go/internal/auth package. If the URL itself includes a username and
...@@ -79,21 +95,23 @@ type Response struct { ...@@ -79,21 +95,23 @@ type Response struct {
// //
// Get returns a non-nil error only if the request did not receive a response // Get returns a non-nil error only if the request did not receive a response
// under any applicable scheme. (A non-2xx response does not cause an error.) // under any applicable scheme. (A non-2xx response does not cause an error.)
func Get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error) { func Get(security SecurityMode, u *url.URL) (*Response, error) {
return get(security, url) return get(security, u)
} }
// PasswordRedacted returns url directly if it does not encode a password, // Redacted returns a redacted string form of the URL,
// or else a copy of url with the password redacted. // suitable for printing in error messages.
func PasswordRedacted(url *urlpkg.URL) *urlpkg.URL { // The string form replaces any non-empty password
if url.User != nil { // in the original URL with "[redacted]".
if _, ok := url.User.Password(); ok { func Redacted(u *url.URL) string {
redacted := *url if u.User != nil {
redacted.User = urlpkg.UserPassword(url.User.Username(), "[redacted]") if _, ok := u.User.Password(); ok {
return &redacted redacted := *u
redacted.User = url.UserPassword(u.User.Username(), "[redacted]")
u = &redacted
} }
} }
return url return u.String()
} }
// OpenBrowser attempts to open the requested URL in a web browser. // OpenBrowser attempts to open the requested URL in a web browser.
......
...@@ -16,8 +16,8 @@ import ( ...@@ -16,8 +16,8 @@ import (
urlpkg "net/url" urlpkg "net/url"
) )
func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error) { func get(security SecurityMode, url *urlpkg.URL) (*Response, error) {
return nil, nil, errors.New("no http in bootstrap go command") return nil, errors.New("no http in bootstrap go command")
} }
func openBrowser(url string) bool { return false } func openBrowser(url string) bool { return false }
...@@ -49,7 +49,7 @@ var securityPreservingHTTPClient = &http.Client{ ...@@ -49,7 +49,7 @@ var securityPreservingHTTPClient = &http.Client{
}, },
} }
func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error) { func get(security SecurityMode, url *urlpkg.URL) (*Response, error) {
fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) { fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) {
if cfg.BuildV { if cfg.BuildV {
log.Printf("Fetching %s", url) log.Printf("Fetching %s", url)
...@@ -90,7 +90,7 @@ func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error) ...@@ -90,7 +90,7 @@ func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error)
if security != Insecure || url.Scheme == "https" { if security != Insecure || url.Scheme == "https" {
// HTTPS failed, and we can't fall back to plain HTTP. // HTTPS failed, and we can't fall back to plain HTTP.
// Report the error from the HTTPS attempt. // Report the error from the HTTPS attempt.
return nil, nil, err return nil, err
} }
} }
} }
...@@ -99,42 +99,44 @@ func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error) ...@@ -99,42 +99,44 @@ func get(security SecurityMode, url *urlpkg.URL) (*urlpkg.URL, *Response, error)
switch url.Scheme { switch url.Scheme {
case "http": case "http":
if security == SecureOnly { if security == SecureOnly {
return nil, nil, fmt.Errorf("URL %q is not secure", PasswordRedacted(url)) return nil, fmt.Errorf("insecure URL: %s", Redacted(url))
} }
case "": case "":
if security != Insecure { if security != Insecure {
panic("should have returned after HTTPS failure") panic("should have returned after HTTPS failure")
} }
default: default:
return nil, nil, fmt.Errorf("unsupported scheme %s", url.Scheme) return nil, fmt.Errorf("unsupported scheme: %s", Redacted(url))
} }
insecure := new(urlpkg.URL) insecure := new(urlpkg.URL)
*insecure = *url *insecure = *url
insecure.Scheme = "http" insecure.Scheme = "http"
if insecure.User != nil && security != Insecure { if insecure.User != nil && security != Insecure {
return nil, nil, fmt.Errorf("refusing to pass credentials to insecure URL %q", PasswordRedacted(insecure)) return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", Redacted(insecure))
} }
fetched, res, err = fetch(insecure) fetched, res, err = fetch(insecure)
if err != nil { if err != nil {
// HTTP failed, and we already tried HTTPS if applicable. // HTTP failed, and we already tried HTTPS if applicable.
// Report the error from the HTTP attempt. // Report the error from the HTTP attempt.
return nil, nil, err return nil, err
} }
} }
// Note: accepting a non-200 OK here, so people can serve a // Note: accepting a non-200 OK here, so people can serve a
// meta import in their http 404 page. // meta import in their http 404 page.
if cfg.BuildV { if cfg.BuildV {
log.Printf("Parsing meta tags from %s (status code %d)", PasswordRedacted(fetched), res.StatusCode) log.Printf("Parsing meta tags from %s (status code %d)", Redacted(fetched), res.StatusCode)
} }
return fetched, &Response{ r := &Response{
URL: Redacted(fetched),
Status: res.Status, Status: res.Status,
StatusCode: res.StatusCode, StatusCode: res.StatusCode,
Header: map[string][]string(res.Header), Header: map[string][]string(res.Header),
Body: res.Body, Body: res.Body,
}, nil }
return r, nil
} }
func openBrowser(url string) bool { return browser.Open(url) } func openBrowser(url string) bool { return browser.Open(url) }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment