1
2
3
4
5
6
7
8
9
10
11
12 package web
13
14 import (
15 "crypto/tls"
16 "errors"
17 "fmt"
18 "io"
19 "mime"
20 "net"
21 "net/http"
22 urlpkg "net/url"
23 "os"
24 "strings"
25 "time"
26
27 "cmd/go/internal/auth"
28 "cmd/go/internal/base"
29 "cmd/go/internal/cfg"
30 "cmd/go/internal/web/intercept"
31 "cmd/internal/browser"
32 )
33
34
35
36
37 var impatientInsecureHTTPClient = &http.Client{
38 CheckRedirect: checkRedirect,
39 Timeout: 5 * time.Second,
40 Transport: &http.Transport{
41 Proxy: http.ProxyFromEnvironment,
42 TLSClientConfig: &tls.Config{
43 InsecureSkipVerify: true,
44 },
45 },
46 }
47
48 var securityPreservingDefaultClient = securityPreservingHTTPClient(http.DefaultClient)
49
50
51
52 func securityPreservingHTTPClient(original *http.Client) *http.Client {
53 c := new(http.Client)
54 *c = *original
55 c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
56 if len(via) > 0 && via[0].URL.Scheme == "https" && req.URL.Scheme != "https" {
57 lastHop := via[len(via)-1].URL
58 return fmt.Errorf("redirected from secure URL %s to insecure URL %s", lastHop, req.URL)
59 }
60 return checkRedirect(req, via)
61 }
62 return c
63 }
64
65 func checkRedirect(req *http.Request, via []*http.Request) error {
66
67
68 if len(via) >= 10 {
69 return errors.New("stopped after 10 redirects")
70 }
71
72 intercept.Request(req)
73 return nil
74 }
75
76 func get(security SecurityMode, url *urlpkg.URL) (*Response, error) {
77 start := time.Now()
78
79 if url.Scheme == "file" {
80 return getFile(url)
81 }
82
83 if intercept.TestHooksEnabled {
84 switch url.Host {
85 case "proxy.golang.org":
86 if os.Getenv("TESTGOPROXY404") == "1" {
87 res := &Response{
88 URL: url.Redacted(),
89 Status: "404 testing",
90 StatusCode: 404,
91 Header: make(map[string][]string),
92 Body: http.NoBody,
93 }
94 if cfg.BuildX {
95 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", url.Redacted(), res.Status, time.Since(start).Seconds())
96 }
97 return res, nil
98 }
99
100 case "localhost.localdev":
101 return nil, fmt.Errorf("no such host localhost.localdev")
102
103 default:
104 if os.Getenv("TESTGONETWORK") == "panic" {
105 if _, ok := intercept.URL(url); !ok {
106 host := url.Host
107 if h, _, err := net.SplitHostPort(url.Host); err == nil && h != "" {
108 host = h
109 }
110 addr := net.ParseIP(host)
111 if addr == nil || (!addr.IsLoopback() && !addr.IsUnspecified()) {
112 panic("use of network: " + url.String())
113 }
114 }
115 }
116 }
117 }
118
119 fetch := func(url *urlpkg.URL) (*http.Response, error) {
120
121
122
123
124 if cfg.BuildX {
125 fmt.Fprintf(os.Stderr, "# get %s\n", url.Redacted())
126 }
127
128 req, err := http.NewRequest("GET", url.String(), nil)
129 if err != nil {
130 return nil, err
131 }
132 t, intercepted := intercept.URL(req.URL)
133 var client *http.Client
134 if security == Insecure && url.Scheme == "https" {
135 client = impatientInsecureHTTPClient
136 } else if intercepted && t.Client != nil {
137 client = securityPreservingHTTPClient(t.Client)
138 } else {
139 client = securityPreservingDefaultClient
140 }
141 if url.Scheme == "https" {
142
143 auth.AddCredentials(client, req, "")
144 }
145 if intercepted {
146 req.Host = req.URL.Host
147 req.URL.Host = t.ToHost
148 }
149
150 release, err := base.AcquireNet()
151 if err != nil {
152 return nil, err
153 }
154 defer func() {
155 if err != nil && release != nil {
156 release()
157 }
158 }()
159 res, err := client.Do(req)
160
161
162
163
164
165 if url.Scheme == "https" && err == nil && res.StatusCode >= 400 && res.StatusCode < 500 {
166
167
168 res.Body.Close()
169 req, err = http.NewRequest("GET", url.String(), nil)
170 if err != nil {
171 return nil, err
172 }
173 auth.AddCredentials(client, req, url.String())
174 intercept.Request(req)
175 res, err = client.Do(req)
176 }
177
178 if err != nil {
179
180
181
182
183 return nil, err
184 }
185
186
187
188 body := res.Body
189 res.Body = hookCloser{
190 ReadCloser: body,
191 afterClose: release,
192 }
193 return res, nil
194 }
195
196 var (
197 fetched *urlpkg.URL
198 res *http.Response
199 err error
200 )
201 if url.Scheme == "" || url.Scheme == "https" {
202 secure := new(urlpkg.URL)
203 *secure = *url
204 secure.Scheme = "https"
205
206 res, err = fetch(secure)
207 if err == nil {
208 fetched = secure
209 } else {
210 if cfg.BuildX {
211 fmt.Fprintf(os.Stderr, "# get %s: %v\n", secure.Redacted(), err)
212 }
213 if security != Insecure || url.Scheme == "https" {
214
215
216 return nil, err
217 }
218 }
219 }
220
221 if res == nil {
222 switch url.Scheme {
223 case "http":
224 if security == SecureOnly {
225 if cfg.BuildX {
226 fmt.Fprintf(os.Stderr, "# get %s: insecure\n", url.Redacted())
227 }
228 return nil, fmt.Errorf("insecure URL: %s", url.Redacted())
229 }
230 case "":
231 if security != Insecure {
232 panic("should have returned after HTTPS failure")
233 }
234 default:
235 if cfg.BuildX {
236 fmt.Fprintf(os.Stderr, "# get %s: unsupported\n", url.Redacted())
237 }
238 return nil, fmt.Errorf("unsupported scheme: %s", url.Redacted())
239 }
240
241 insecure := new(urlpkg.URL)
242 *insecure = *url
243 insecure.Scheme = "http"
244 if insecure.User != nil && security != Insecure {
245 if cfg.BuildX {
246 fmt.Fprintf(os.Stderr, "# get %s: insecure credentials\n", insecure.Redacted())
247 }
248 return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", insecure.Redacted())
249 }
250
251 res, err = fetch(insecure)
252 if err == nil {
253 fetched = insecure
254 } else {
255 if cfg.BuildX {
256 fmt.Fprintf(os.Stderr, "# get %s: %v\n", insecure.Redacted(), err)
257 }
258
259
260 return nil, err
261 }
262 }
263
264
265
266 if cfg.BuildX {
267 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", fetched.Redacted(), res.Status, time.Since(start).Seconds())
268 }
269
270 r := &Response{
271 URL: fetched.Redacted(),
272 Status: res.Status,
273 StatusCode: res.StatusCode,
274 Header: map[string][]string(res.Header),
275 Body: res.Body,
276 }
277
278 if res.StatusCode != http.StatusOK {
279 contentType := res.Header.Get("Content-Type")
280 if mediaType, params, _ := mime.ParseMediaType(contentType); mediaType == "text/plain" {
281 switch charset := strings.ToLower(params["charset"]); charset {
282 case "us-ascii", "utf-8", "":
283
284
285 r.errorDetail.r = res.Body
286 r.Body = &r.errorDetail
287 }
288 }
289 }
290
291 return r, nil
292 }
293
294 func getFile(u *urlpkg.URL) (*Response, error) {
295 path, err := urlToFilePath(u)
296 if err != nil {
297 return nil, err
298 }
299 f, err := os.Open(path)
300
301 if os.IsNotExist(err) {
302 return &Response{
303 URL: u.Redacted(),
304 Status: http.StatusText(http.StatusNotFound),
305 StatusCode: http.StatusNotFound,
306 Body: http.NoBody,
307 fileErr: err,
308 }, nil
309 }
310
311 if os.IsPermission(err) {
312 return &Response{
313 URL: u.Redacted(),
314 Status: http.StatusText(http.StatusForbidden),
315 StatusCode: http.StatusForbidden,
316 Body: http.NoBody,
317 fileErr: err,
318 }, nil
319 }
320
321 if err != nil {
322 return nil, err
323 }
324
325 return &Response{
326 URL: u.Redacted(),
327 Status: http.StatusText(http.StatusOK),
328 StatusCode: http.StatusOK,
329 Body: f,
330 }, nil
331 }
332
333 func openBrowser(url string) bool { return browser.Open(url) }
334
335 func isLocalHost(u *urlpkg.URL) bool {
336
337
338
339 host, _, err := net.SplitHostPort(u.Host)
340 if err != nil {
341 host = u.Host
342 }
343 if host == "localhost" {
344 return true
345 }
346 if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
347 return true
348 }
349 return false
350 }
351
352 type hookCloser struct {
353 io.ReadCloser
354 afterClose func()
355 }
356
357 func (c hookCloser) Close() error {
358 err := c.ReadCloser.Close()
359 c.afterClose()
360 return err
361 }
362
View as plain text