github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/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/bir3/gocompiler/src/cmd/gocmd/internal/auth" 27 "github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg" 28 "github.com/bir3/gocompiler/src/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 CheckRedirect: checkRedirect, 36 Timeout: 5 * time.Second, 37 Transport: &http.Transport{ 38 Proxy: http.ProxyFromEnvironment, 39 TLSClientConfig: &tls.Config{ 40 InsecureSkipVerify: true, 41 }, 42 }, 43 } 44 45 var securityPreservingDefaultClient = securityPreservingHTTPClient(http.DefaultClient) 46 47 // securityPreservingDefaultClient returns a client that is like the original 48 // but rejects redirects to plain-HTTP URLs if the original URL was secure. 49 func securityPreservingHTTPClient(original *http.Client) *http.Client { 50 c := new(http.Client) 51 *c = *original 52 c.CheckRedirect = func(req *http.Request, via []*http.Request) error { 53 if len(via) > 0 && via[0].URL.Scheme == "https" && req.URL.Scheme != "https" { 54 lastHop := via[len(via)-1].URL 55 return fmt.Errorf("redirected from secure URL %s to insecure URL %s", lastHop, req.URL) 56 } 57 return checkRedirect(req, via) 58 } 59 return c 60 } 61 62 func checkRedirect(req *http.Request, via []*http.Request) error { 63 // Go's http.DefaultClient allows 10 redirects before returning an error. 64 // Mimic that behavior here. 65 if len(via) >= 10 { 66 return errors.New("stopped after 10 redirects") 67 } 68 69 interceptRequest(req) 70 return nil 71 } 72 73 type Interceptor struct { 74 Scheme string 75 FromHost string 76 ToHost string 77 Client *http.Client 78 } 79 80 func EnableTestHooks(interceptors []Interceptor) error { 81 if enableTestHooks { 82 return errors.New("web: test hooks already enabled") 83 } 84 85 for _, t := range interceptors { 86 if t.FromHost == "" { 87 panic("EnableTestHooks: missing FromHost") 88 } 89 if t.ToHost == "" { 90 panic("EnableTestHooks: missing ToHost") 91 } 92 } 93 94 testInterceptors = interceptors 95 enableTestHooks = true 96 return nil 97 } 98 99 func DisableTestHooks() { 100 if !enableTestHooks { 101 panic("web: test hooks not enabled") 102 } 103 enableTestHooks = false 104 testInterceptors = nil 105 } 106 107 var ( 108 enableTestHooks = false 109 testInterceptors []Interceptor 110 ) 111 112 func interceptURL(u *urlpkg.URL) (*Interceptor, bool) { 113 if !enableTestHooks { 114 return nil, false 115 } 116 for i, t := range testInterceptors { 117 if u.Host == t.FromHost && (t.Scheme == "" || u.Scheme == t.Scheme) { 118 return &testInterceptors[i], true 119 } 120 } 121 return nil, false 122 } 123 124 func interceptRequest(req *http.Request) { 125 if t, ok := interceptURL(req.URL); ok { 126 req.Host = req.URL.Host 127 req.URL.Host = t.ToHost 128 } 129 } 130 131 func get(security SecurityMode, url *urlpkg.URL) (*Response, error) { 132 start := time.Now() 133 134 if url.Scheme == "file" { 135 return getFile(url) 136 } 137 138 if enableTestHooks { 139 switch url.Host { 140 case "proxy.golang.org": 141 if os.Getenv("TESTGOPROXY404") == "1" { 142 res := &Response{ 143 URL: url.Redacted(), 144 Status: "404 testing", 145 StatusCode: 404, 146 Header: make(map[string][]string), 147 Body: http.NoBody, 148 } 149 if cfg.BuildX { 150 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", url.Redacted(), res.Status, time.Since(start).Seconds()) 151 } 152 return res, nil 153 } 154 155 case "localhost.localdev": 156 return nil, fmt.Errorf("no such host localhost.localdev") 157 158 default: 159 if os.Getenv("TESTGONETWORK") == "panic" { 160 if _, ok := interceptURL(url); !ok { 161 host := url.Host 162 if h, _, err := net.SplitHostPort(url.Host); err == nil && h != "" { 163 host = h 164 } 165 addr := net.ParseIP(host) 166 if addr == nil || (!addr.IsLoopback() && !addr.IsUnspecified()) { 167 panic("use of network: " + url.String()) 168 } 169 } 170 } 171 } 172 } 173 174 fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) { 175 // Note: The -v build flag does not mean "print logging information", 176 // despite its historical misuse for this in GOPATH-based go get. 177 // We print extra logging in -x mode instead, which traces what 178 // commands are executed. 179 if cfg.BuildX { 180 fmt.Fprintf(os.Stderr, "# get %s\n", url.Redacted()) 181 } 182 183 req, err := http.NewRequest("GET", url.String(), nil) 184 if err != nil { 185 return nil, nil, err 186 } 187 if url.Scheme == "https" { 188 auth.AddCredentials(req) 189 } 190 t, intercepted := interceptURL(req.URL) 191 if intercepted { 192 req.Host = req.URL.Host 193 req.URL.Host = t.ToHost 194 } 195 196 var res *http.Response 197 if security == Insecure && url.Scheme == "https" { // fail earlier 198 res, err = impatientInsecureHTTPClient.Do(req) 199 } else { 200 if intercepted && t.Client != nil { 201 client := securityPreservingHTTPClient(t.Client) 202 res, err = client.Do(req) 203 } else { 204 res, err = securityPreservingDefaultClient.Do(req) 205 } 206 } 207 return url, res, err 208 } 209 210 var ( 211 fetched *urlpkg.URL 212 res *http.Response 213 err error 214 ) 215 if url.Scheme == "" || url.Scheme == "https" { 216 secure := new(urlpkg.URL) 217 *secure = *url 218 secure.Scheme = "https" 219 220 fetched, res, err = fetch(secure) 221 if err != nil { 222 if cfg.BuildX { 223 fmt.Fprintf(os.Stderr, "# get %s: %v\n", secure.Redacted(), err) 224 } 225 if security != Insecure || url.Scheme == "https" { 226 // HTTPS failed, and we can't fall back to plain HTTP. 227 // Report the error from the HTTPS attempt. 228 return nil, err 229 } 230 } 231 } 232 233 if res == nil { 234 switch url.Scheme { 235 case "http": 236 if security == SecureOnly { 237 if cfg.BuildX { 238 fmt.Fprintf(os.Stderr, "# get %s: insecure\n", url.Redacted()) 239 } 240 return nil, fmt.Errorf("insecure URL: %s", url.Redacted()) 241 } 242 case "": 243 if security != Insecure { 244 panic("should have returned after HTTPS failure") 245 } 246 default: 247 if cfg.BuildX { 248 fmt.Fprintf(os.Stderr, "# get %s: unsupported\n", url.Redacted()) 249 } 250 return nil, fmt.Errorf("unsupported scheme: %s", url.Redacted()) 251 } 252 253 insecure := new(urlpkg.URL) 254 *insecure = *url 255 insecure.Scheme = "http" 256 if insecure.User != nil && security != Insecure { 257 if cfg.BuildX { 258 fmt.Fprintf(os.Stderr, "# get %s: insecure credentials\n", insecure.Redacted()) 259 } 260 return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", insecure.Redacted()) 261 } 262 263 fetched, res, err = fetch(insecure) 264 if err != nil { 265 if cfg.BuildX { 266 fmt.Fprintf(os.Stderr, "# get %s: %v\n", insecure.Redacted(), err) 267 } 268 // HTTP failed, and we already tried HTTPS if applicable. 269 // Report the error from the HTTP attempt. 270 return nil, err 271 } 272 } 273 274 // Note: accepting a non-200 OK here, so people can serve a 275 // meta import in their http 404 page. 276 if cfg.BuildX { 277 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", fetched.Redacted(), res.Status, time.Since(start).Seconds()) 278 } 279 280 r := &Response{ 281 URL: fetched.Redacted(), 282 Status: res.Status, 283 StatusCode: res.StatusCode, 284 Header: map[string][]string(res.Header), 285 Body: res.Body, 286 } 287 288 if res.StatusCode != http.StatusOK { 289 contentType := res.Header.Get("Content-Type") 290 if mediaType, params, _ := mime.ParseMediaType(contentType); mediaType == "text/plain" { 291 switch charset := strings.ToLower(params["charset"]); charset { 292 case "us-ascii", "utf-8", "": 293 // Body claims to be plain text in UTF-8 or a subset thereof. 294 // Try to extract a useful error message from it. 295 r.errorDetail.r = res.Body 296 r.Body = &r.errorDetail 297 } 298 } 299 } 300 301 return r, nil 302 } 303 304 func getFile(u *urlpkg.URL) (*Response, error) { 305 path, err := urlToFilePath(u) 306 if err != nil { 307 return nil, err 308 } 309 f, err := os.Open(path) 310 311 if os.IsNotExist(err) { 312 return &Response{ 313 URL: u.Redacted(), 314 Status: http.StatusText(http.StatusNotFound), 315 StatusCode: http.StatusNotFound, 316 Body: http.NoBody, 317 fileErr: err, 318 }, nil 319 } 320 321 if os.IsPermission(err) { 322 return &Response{ 323 URL: u.Redacted(), 324 Status: http.StatusText(http.StatusForbidden), 325 StatusCode: http.StatusForbidden, 326 Body: http.NoBody, 327 fileErr: err, 328 }, nil 329 } 330 331 if err != nil { 332 return nil, err 333 } 334 335 return &Response{ 336 URL: u.Redacted(), 337 Status: http.StatusText(http.StatusOK), 338 StatusCode: http.StatusOK, 339 Body: f, 340 }, nil 341 } 342 343 func openBrowser(url string) bool { return browser.Open(url) } 344 345 func isLocalHost(u *urlpkg.URL) bool { 346 // VCSTestRepoURL itself is secure, and it may redirect requests to other 347 // ports (such as a port serving the "svn" protocol) which should also be 348 // considered secure. 349 host, _, err := net.SplitHostPort(u.Host) 350 if err != nil { 351 host = u.Host 352 } 353 if host == "localhost" { 354 return true 355 } 356 if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() { 357 return true 358 } 359 return false 360 }