github.com/knieriem/gointernal@v0.2.0-pre2/cmd/go/internal/web/http.go (about) 1 // Copyright 2012 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:build !cmd_go_bootstrap 6 7 // This code is compiled into the real 'go' binary, but it is not 8 // compiled into the binary that is built during all.bash, so as 9 // to avoid needing to build net (and thus use cgo) during the 10 // bootstrap process. 11 12 package web 13 14 import ( 15 "crypto/tls" 16 "errors" 17 "fmt" 18 "mime" 19 "net" 20 "net/http" 21 urlpkg "net/url" 22 "os" 23 "strings" 24 "time" 25 26 "github.com/knieriem/gointernal/cmd/go/cfg" 27 "github.com/knieriem/gointernal/cmd/go/internal/auth" 28 "github.com/knieriem/gointernal/cmd/internal/browser" 29 ) 30 31 // impatientInsecureHTTPClient is used with GOINSECURE, 32 // when we're connecting to https servers that might not be there 33 // or might be using self-signed certificates. 34 var impatientInsecureHTTPClient = &http.Client{ 35 Timeout: 5 * time.Second, 36 Transport: &http.Transport{ 37 Proxy: http.ProxyFromEnvironment, 38 TLSClientConfig: &tls.Config{ 39 InsecureSkipVerify: true, 40 }, 41 }, 42 } 43 44 // securityPreservingHTTPClient is like the default HTTP client, but rejects 45 // redirects to plain-HTTP URLs if the original URL was secure. 46 var securityPreservingHTTPClient = &http.Client{ 47 CheckRedirect: func(req *http.Request, via []*http.Request) error { 48 if len(via) > 0 && via[0].URL.Scheme == "https" && req.URL.Scheme != "https" { 49 lastHop := via[len(via)-1].URL 50 return fmt.Errorf("redirected from secure URL %s to insecure URL %s", lastHop, req.URL) 51 } 52 53 // Go's http.DefaultClient allows 10 redirects before returning an error. 54 // The securityPreservingHTTPClient also uses this default policy to avoid 55 // Go command hangs. 56 if len(via) >= 10 { 57 return errors.New("stopped after 10 redirects") 58 } 59 return nil 60 }, 61 } 62 63 func get(security SecurityMode, url *urlpkg.URL) (*Response, error) { 64 start := time.Now() 65 66 if url.Scheme == "file" { 67 return getFile(url) 68 } 69 70 if os.Getenv("TESTGOPROXY404") == "1" && url.Host == "proxy.golang.org" { 71 res := &Response{ 72 URL: url.Redacted(), 73 Status: "404 testing", 74 StatusCode: 404, 75 Header: make(map[string][]string), 76 Body: http.NoBody, 77 } 78 if cfg.BuildX { 79 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", url.Redacted(), res.Status, time.Since(start).Seconds()) 80 } 81 return res, nil 82 } 83 84 if url.Host == "localhost.localdev" { 85 return nil, fmt.Errorf("no such host localhost.localdev") 86 } 87 if os.Getenv("TESTGONETWORK") == "panic" { 88 host := url.Host 89 if h, _, err := net.SplitHostPort(url.Host); err == nil && h != "" { 90 host = h 91 } 92 addr := net.ParseIP(host) 93 if addr == nil || (!addr.IsLoopback() && !addr.IsUnspecified()) { 94 panic("use of network: " + url.String()) 95 } 96 } 97 98 fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) { 99 // Note: The -v build flag does not mean "print logging information", 100 // despite its historical misuse for this in GOPATH-based go get. 101 // We print extra logging in -x mode instead, which traces what 102 // commands are executed. 103 if cfg.BuildX { 104 fmt.Fprintf(os.Stderr, "# get %s\n", url.Redacted()) 105 } 106 107 req, err := http.NewRequest("GET", url.String(), nil) 108 if err != nil { 109 return nil, nil, err 110 } 111 if url.Scheme == "https" { 112 auth.AddCredentials(req) 113 } 114 115 var res *http.Response 116 if security == Insecure && url.Scheme == "https" { // fail earlier 117 res, err = impatientInsecureHTTPClient.Do(req) 118 } else { 119 res, err = securityPreservingHTTPClient.Do(req) 120 } 121 return url, res, err 122 } 123 124 var ( 125 fetched *urlpkg.URL 126 res *http.Response 127 err error 128 ) 129 if url.Scheme == "" || url.Scheme == "https" { 130 secure := new(urlpkg.URL) 131 *secure = *url 132 secure.Scheme = "https" 133 134 fetched, res, err = fetch(secure) 135 if err != nil { 136 if cfg.BuildX { 137 fmt.Fprintf(os.Stderr, "# get %s: %v\n", secure.Redacted(), err) 138 } 139 if security != Insecure || url.Scheme == "https" { 140 // HTTPS failed, and we can't fall back to plain HTTP. 141 // Report the error from the HTTPS attempt. 142 return nil, err 143 } 144 } 145 } 146 147 if res == nil { 148 switch url.Scheme { 149 case "http": 150 if security == SecureOnly { 151 if cfg.BuildX { 152 fmt.Fprintf(os.Stderr, "# get %s: insecure\n", url.Redacted()) 153 } 154 return nil, fmt.Errorf("insecure URL: %s", url.Redacted()) 155 } 156 case "": 157 if security != Insecure { 158 panic("should have returned after HTTPS failure") 159 } 160 default: 161 if cfg.BuildX { 162 fmt.Fprintf(os.Stderr, "# get %s: unsupported\n", url.Redacted()) 163 } 164 return nil, fmt.Errorf("unsupported scheme: %s", url.Redacted()) 165 } 166 167 insecure := new(urlpkg.URL) 168 *insecure = *url 169 insecure.Scheme = "http" 170 if insecure.User != nil && security != Insecure { 171 if cfg.BuildX { 172 fmt.Fprintf(os.Stderr, "# get %s: insecure credentials\n", insecure.Redacted()) 173 } 174 return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", insecure.Redacted()) 175 } 176 177 fetched, res, err = fetch(insecure) 178 if err != nil { 179 if cfg.BuildX { 180 fmt.Fprintf(os.Stderr, "# get %s: %v\n", insecure.Redacted(), err) 181 } 182 // HTTP failed, and we already tried HTTPS if applicable. 183 // Report the error from the HTTP attempt. 184 return nil, err 185 } 186 } 187 188 // Note: accepting a non-200 OK here, so people can serve a 189 // meta import in their http 404 page. 190 if cfg.BuildX { 191 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", fetched.Redacted(), res.Status, time.Since(start).Seconds()) 192 } 193 194 r := &Response{ 195 URL: fetched.Redacted(), 196 Status: res.Status, 197 StatusCode: res.StatusCode, 198 Header: map[string][]string(res.Header), 199 Body: res.Body, 200 } 201 202 if res.StatusCode != http.StatusOK { 203 contentType := res.Header.Get("Content-Type") 204 if mediaType, params, _ := mime.ParseMediaType(contentType); mediaType == "text/plain" { 205 switch charset := strings.ToLower(params["charset"]); charset { 206 case "us-ascii", "utf-8", "": 207 // Body claims to be plain text in UTF-8 or a subset thereof. 208 // Try to extract a useful error message from it. 209 r.errorDetail.r = res.Body 210 r.Body = &r.errorDetail 211 } 212 } 213 } 214 215 return r, nil 216 } 217 218 func getFile(u *urlpkg.URL) (*Response, error) { 219 path, err := urlToFilePath(u) 220 if err != nil { 221 return nil, err 222 } 223 f, err := os.Open(path) 224 225 if os.IsNotExist(err) { 226 return &Response{ 227 URL: u.Redacted(), 228 Status: http.StatusText(http.StatusNotFound), 229 StatusCode: http.StatusNotFound, 230 Body: http.NoBody, 231 fileErr: err, 232 }, nil 233 } 234 235 if os.IsPermission(err) { 236 return &Response{ 237 URL: u.Redacted(), 238 Status: http.StatusText(http.StatusForbidden), 239 StatusCode: http.StatusForbidden, 240 Body: http.NoBody, 241 fileErr: err, 242 }, nil 243 } 244 245 if err != nil { 246 return nil, err 247 } 248 249 return &Response{ 250 URL: u.Redacted(), 251 Status: http.StatusText(http.StatusOK), 252 StatusCode: http.StatusOK, 253 Body: f, 254 }, nil 255 } 256 257 func openBrowser(url string) bool { return browser.Open(url) }