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