1 // Copyright 2017 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 web defines minimal helper routines for accessing HTTP/HTTPS 6 // resources without requiring external dependenicies on the net package. 7 // 8 // If the cmd_go_bootstrap build tag is present, web avoids the use of the net 9 // package and returns errors for all network operations. 10 package web 11 12 import ( 13 "fmt" 14 "io" 15 "io/ioutil" 16 "net/url" 17 "os" 18 "strings" 19 ) 20 21 // SecurityMode specifies whether a function should make network 22 // calls using insecure transports (eg, plain text HTTP). 23 // The zero value is "secure". 24 type SecurityMode int 25 26 const ( 27 SecureOnly SecurityMode = iota // Reject plain HTTP; validate HTTPS. 28 DefaultSecurity // Allow plain HTTP if explicit; validate HTTPS. 29 Insecure // Allow plain HTTP if not explicitly HTTPS; skip HTTPS validation. 30 ) 31 32 // An HTTPError describes an HTTP error response (non-200 result). 33 type HTTPError struct { 34 URL string // redacted 35 Status string 36 StatusCode int 37 } 38 39 func (e *HTTPError) Error() string { 40 return fmt.Sprintf("reading %s: %v", e.URL, e.Status) 41 } 42 43 func (e *HTTPError) Is(target error) bool { 44 return target == os.ErrNotExist && (e.StatusCode == 404 || e.StatusCode == 410) 45 } 46 47 // GetBytes returns the body of the requested resource, or an error if the 48 // response status was not http.StatusOK. 49 // 50 // GetBytes is a convenience wrapper around Get and Response.Err. 51 func GetBytes(u *url.URL) ([]byte, error) { 52 resp, err := Get(DefaultSecurity, u) 53 if err != nil { 54 return nil, err 55 } 56 defer resp.Body.Close() 57 if err := resp.Err(); err != nil { 58 return nil, err 59 } 60 b, err := ioutil.ReadAll(resp.Body) 61 if err != nil { 62 return nil, fmt.Errorf("reading %s: %v", Redacted(u), err) 63 } 64 return b, nil 65 } 66 67 type Response struct { 68 URL string // redacted 69 Status string 70 StatusCode int 71 Header map[string][]string 72 Body io.ReadCloser 73 } 74 75 // Err returns an *HTTPError corresponding to the response r. 76 // It returns nil if the response r has StatusCode 200 or 0 (unset). 77 func (r *Response) Err() error { 78 if r.StatusCode == 200 || r.StatusCode == 0 { 79 return nil 80 } 81 return &HTTPError{URL: r.URL, Status: r.Status, StatusCode: r.StatusCode} 82 } 83 84 // Get returns the body of the HTTP or HTTPS resource specified at the given URL. 85 // 86 // If the URL does not include an explicit scheme, Get first tries "https". 87 // If the server does not respond under that scheme and the security mode is 88 // Insecure, Get then tries "http". 89 // The URL included in the response indicates which scheme was actually used, 90 // and it is a redacted URL suitable for use in error messages. 91 // 92 // For the "https" scheme only, credentials are attached using the 93 // cmd/go/internal/auth package. If the URL itself includes a username and 94 // password, it will not be attempted under the "http" scheme unless the 95 // security mode is Insecure. 96 // 97 // Get returns a non-nil error only if the request did not receive a response 98 // under any applicable scheme. (A non-2xx response does not cause an error.) 99 func Get(security SecurityMode, u *url.URL) (*Response, error) { 100 return get(security, u) 101 } 102 103 // Redacted returns a redacted string form of the URL, 104 // suitable for printing in error messages. 105 // The string form replaces any non-empty password 106 // in the original URL with "[redacted]". 107 func Redacted(u *url.URL) string { 108 if u.User != nil { 109 if _, ok := u.User.Password(); ok { 110 redacted := *u 111 redacted.User = url.UserPassword(u.User.Username(), "[redacted]") 112 u = &redacted 113 } 114 } 115 return u.String() 116 } 117 118 // OpenBrowser attempts to open the requested URL in a web browser. 119 func OpenBrowser(url string) (opened bool) { 120 return openBrowser(url) 121 } 122 123 // Join returns the result of adding the slash-separated 124 // path elements to the end of u's path. 125 func Join(u *url.URL, path string) *url.URL { 126 j := *u 127 if path == "" { 128 return &j 129 } 130 j.Path = strings.TrimSuffix(u.Path, "/") + "/" + strings.TrimPrefix(path, "/") 131 j.RawPath = strings.TrimSuffix(u.RawPath, "/") + "/" + strings.TrimPrefix(path, "/") 132 return &j 133 } 134