1
2
3
4
5
6
7
8
9
10
11
12
13 package web
14
15 import (
16 "crypto/tls"
17 "errors"
18 "fmt"
19 "mime"
20 "net/http"
21 urlpkg "net/url"
22 "os"
23 "strings"
24 "time"
25
26 "cmd/go/internal/auth"
27 "cmd/go/internal/cfg"
28 "cmd/internal/browser"
29 )
30
31
32
33
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
45
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
54
55
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" && !strings.HasPrefix(url.Host, "127.0.0.1") && !strings.HasPrefix(url.Host, "0.0.0.0") {
88 panic("use of network: " + url.String())
89 }
90
91 fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) {
92
93
94
95
96 if cfg.BuildX {
97 fmt.Fprintf(os.Stderr, "# get %s\n", url.Redacted())
98 }
99
100 req, err := http.NewRequest("GET", url.String(), nil)
101 if err != nil {
102 return nil, nil, err
103 }
104 if url.Scheme == "https" {
105 auth.AddCredentials(req)
106 }
107
108 var res *http.Response
109 if security == Insecure && url.Scheme == "https" {
110 res, err = impatientInsecureHTTPClient.Do(req)
111 } else {
112 res, err = securityPreservingHTTPClient.Do(req)
113 }
114 return url, res, err
115 }
116
117 var (
118 fetched *urlpkg.URL
119 res *http.Response
120 err error
121 )
122 if url.Scheme == "" || url.Scheme == "https" {
123 secure := new(urlpkg.URL)
124 *secure = *url
125 secure.Scheme = "https"
126
127 fetched, res, err = fetch(secure)
128 if err != nil {
129 if cfg.BuildX {
130 fmt.Fprintf(os.Stderr, "# get %s: %v\n", secure.Redacted(), err)
131 }
132 if security != Insecure || url.Scheme == "https" {
133
134
135 return nil, err
136 }
137 }
138 }
139
140 if res == nil {
141 switch url.Scheme {
142 case "http":
143 if security == SecureOnly {
144 if cfg.BuildX {
145 fmt.Fprintf(os.Stderr, "# get %s: insecure\n", url.Redacted())
146 }
147 return nil, fmt.Errorf("insecure URL: %s", url.Redacted())
148 }
149 case "":
150 if security != Insecure {
151 panic("should have returned after HTTPS failure")
152 }
153 default:
154 if cfg.BuildX {
155 fmt.Fprintf(os.Stderr, "# get %s: unsupported\n", url.Redacted())
156 }
157 return nil, fmt.Errorf("unsupported scheme: %s", url.Redacted())
158 }
159
160 insecure := new(urlpkg.URL)
161 *insecure = *url
162 insecure.Scheme = "http"
163 if insecure.User != nil && security != Insecure {
164 if cfg.BuildX {
165 fmt.Fprintf(os.Stderr, "# get %s: insecure credentials\n", insecure.Redacted())
166 }
167 return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", insecure.Redacted())
168 }
169
170 fetched, res, err = fetch(insecure)
171 if err != nil {
172 if cfg.BuildX {
173 fmt.Fprintf(os.Stderr, "# get %s: %v\n", insecure.Redacted(), err)
174 }
175
176
177 return nil, err
178 }
179 }
180
181
182
183 if cfg.BuildX {
184 fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", fetched.Redacted(), res.Status, time.Since(start).Seconds())
185 }
186
187 r := &Response{
188 URL: fetched.Redacted(),
189 Status: res.Status,
190 StatusCode: res.StatusCode,
191 Header: map[string][]string(res.Header),
192 Body: res.Body,
193 }
194
195 if res.StatusCode != http.StatusOK {
196 contentType := res.Header.Get("Content-Type")
197 if mediaType, params, _ := mime.ParseMediaType(contentType); mediaType == "text/plain" {
198 switch charset := strings.ToLower(params["charset"]); charset {
199 case "us-ascii", "utf-8", "":
200
201
202 r.errorDetail.r = res.Body
203 r.Body = &r.errorDetail
204 }
205 }
206 }
207
208 return r, nil
209 }
210
211 func getFile(u *urlpkg.URL) (*Response, error) {
212 path, err := urlToFilePath(u)
213 if err != nil {
214 return nil, err
215 }
216 f, err := os.Open(path)
217
218 if os.IsNotExist(err) {
219 return &Response{
220 URL: u.Redacted(),
221 Status: http.StatusText(http.StatusNotFound),
222 StatusCode: http.StatusNotFound,
223 Body: http.NoBody,
224 fileErr: err,
225 }, nil
226 }
227
228 if os.IsPermission(err) {
229 return &Response{
230 URL: u.Redacted(),
231 Status: http.StatusText(http.StatusForbidden),
232 StatusCode: http.StatusForbidden,
233 Body: http.NoBody,
234 fileErr: err,
235 }, nil
236 }
237
238 if err != nil {
239 return nil, err
240 }
241
242 return &Response{
243 URL: u.Redacted(),
244 Status: http.StatusText(http.StatusOK),
245 StatusCode: http.StatusOK,
246 Body: f,
247 }, nil
248 }
249
250 func openBrowser(url string) bool { return browser.Open(url) }
251
View as plain text