...

Source file src/pkg/cmd/go/internal/web/http.go

     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		"io/ioutil"
    18		"net/http"
    19		urlpkg "net/url"
    20		"os"
    21		"strings"
    22		"time"
    23	
    24		"cmd/go/internal/auth"
    25		"cmd/go/internal/cfg"
    26		"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:       ioutil.NopCloser(strings.NewReader("")),
    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(url), 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(url))
   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(url), 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(url), res.Status, time.Since(start).Seconds())
   169		}
   170		r := &Response{
   171			URL:        Redacted(fetched),
   172			Status:     res.Status,
   173			StatusCode: res.StatusCode,
   174			Header:     map[string][]string(res.Header),
   175			Body:       res.Body,
   176		}
   177		return r, nil
   178	}
   179	
   180	func getFile(u *urlpkg.URL) (*Response, error) {
   181		path, err := urlToFilePath(u)
   182		if err != nil {
   183			return nil, err
   184		}
   185		f, err := os.Open(path)
   186	
   187		if os.IsNotExist(err) {
   188			return &Response{
   189				URL:        Redacted(u),
   190				Status:     http.StatusText(http.StatusNotFound),
   191				StatusCode: http.StatusNotFound,
   192				Body:       http.NoBody,
   193			}, nil
   194		}
   195	
   196		if os.IsPermission(err) {
   197			return &Response{
   198				URL:        Redacted(u),
   199				Status:     http.StatusText(http.StatusForbidden),
   200				StatusCode: http.StatusForbidden,
   201				Body:       http.NoBody,
   202			}, nil
   203		}
   204	
   205		if err != nil {
   206			return nil, err
   207		}
   208	
   209		return &Response{
   210			URL:        Redacted(u),
   211			Status:     http.StatusText(http.StatusOK),
   212			StatusCode: http.StatusOK,
   213			Body:       f,
   214		}, nil
   215	}
   216	
   217	func openBrowser(url string) bool { return browser.Open(url) }
   218	

View as plain text