Black Lives Matter. Support the Equal Justice Initiative.

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

Documentation: cmd/go/internal/web

     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  //go:build !cmd_go_bootstrap
     6  // +build !cmd_go_bootstrap
     7  
     8  // This code is compiled into the real 'go' binary, but it is not
     9  // compiled into the binary that is built during all.bash, so as
    10  // to avoid needing to build net (and thus use cgo) during the
    11  // bootstrap process.
    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  // impatientInsecureHTTPClient is used with GOINSECURE,
    32  // when we're connecting to https servers that might not be there
    33  // or might be using self-signed certificates.
    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  // securityPreservingHTTPClient is like the default HTTP client, but rejects
    45  // redirects to plain-HTTP URLs if the original URL was secure.
    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  		// Go's http.DefaultClient allows 10 redirects before returning an error.
    54  		// The securityPreservingHTTPClient also uses this default policy to avoid
    55  		// Go command hangs.
    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  		// Note: The -v build flag does not mean "print logging information",
    93  		// despite its historical misuse for this in GOPATH-based go get.
    94  		// We print extra logging in -x mode instead, which traces what
    95  		// commands are executed.
    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" { // fail earlier
   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  				// HTTPS failed, and we can't fall back to plain HTTP.
   134  				// Report the error from the HTTPS attempt.
   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  			// HTTP failed, and we already tried HTTPS if applicable.
   176  			// Report the error from the HTTP attempt.
   177  			return nil, err
   178  		}
   179  	}
   180  
   181  	// Note: accepting a non-200 OK here, so people can serve a
   182  	// meta import in their http 404 page.
   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  				// Body claims to be plain text in UTF-8 or a subset thereof.
   201  				// Try to extract a useful error message from it.
   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