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 "mime"
19 "net"
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" {
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
100
101
102
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" {
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
141
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
183
184 return nil, err
185 }
186 }
187
188
189
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
208
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) }
258
View as plain text