github.com/bir3/gocompiler@v0.3.205/src/cmd/gocmd/internal/web/http.go (about)

     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  
     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  	"errors"
    17  	"fmt"
    18  	"mime"
    19  	"net"
    20  	"net/http"
    21  	urlpkg "net/url"
    22  	"os"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/auth"
    27  	"github.com/bir3/gocompiler/src/cmd/gocmd/internal/cfg"
    28  	"github.com/bir3/gocompiler/src/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  	CheckRedirect: checkRedirect,
    36  	Timeout:       5 * time.Second,
    37  	Transport: &http.Transport{
    38  		Proxy: http.ProxyFromEnvironment,
    39  		TLSClientConfig: &tls.Config{
    40  			InsecureSkipVerify: true,
    41  		},
    42  	},
    43  }
    44  
    45  var securityPreservingDefaultClient = securityPreservingHTTPClient(http.DefaultClient)
    46  
    47  // securityPreservingDefaultClient returns a client that is like the original
    48  // but rejects redirects to plain-HTTP URLs if the original URL was secure.
    49  func securityPreservingHTTPClient(original *http.Client) *http.Client {
    50  	c := new(http.Client)
    51  	*c = *original
    52  	c.CheckRedirect = func(req *http.Request, via []*http.Request) error {
    53  		if len(via) > 0 && via[0].URL.Scheme == "https" && req.URL.Scheme != "https" {
    54  			lastHop := via[len(via)-1].URL
    55  			return fmt.Errorf("redirected from secure URL %s to insecure URL %s", lastHop, req.URL)
    56  		}
    57  		return checkRedirect(req, via)
    58  	}
    59  	return c
    60  }
    61  
    62  func checkRedirect(req *http.Request, via []*http.Request) error {
    63  	// Go's http.DefaultClient allows 10 redirects before returning an error.
    64  	// Mimic that behavior here.
    65  	if len(via) >= 10 {
    66  		return errors.New("stopped after 10 redirects")
    67  	}
    68  
    69  	interceptRequest(req)
    70  	return nil
    71  }
    72  
    73  type Interceptor struct {
    74  	Scheme   string
    75  	FromHost string
    76  	ToHost   string
    77  	Client   *http.Client
    78  }
    79  
    80  func EnableTestHooks(interceptors []Interceptor) error {
    81  	if enableTestHooks {
    82  		return errors.New("web: test hooks already enabled")
    83  	}
    84  
    85  	for _, t := range interceptors {
    86  		if t.FromHost == "" {
    87  			panic("EnableTestHooks: missing FromHost")
    88  		}
    89  		if t.ToHost == "" {
    90  			panic("EnableTestHooks: missing ToHost")
    91  		}
    92  	}
    93  
    94  	testInterceptors = interceptors
    95  	enableTestHooks = true
    96  	return nil
    97  }
    98  
    99  func DisableTestHooks() {
   100  	if !enableTestHooks {
   101  		panic("web: test hooks not enabled")
   102  	}
   103  	enableTestHooks = false
   104  	testInterceptors = nil
   105  }
   106  
   107  var (
   108  	enableTestHooks  = false
   109  	testInterceptors []Interceptor
   110  )
   111  
   112  func interceptURL(u *urlpkg.URL) (*Interceptor, bool) {
   113  	if !enableTestHooks {
   114  		return nil, false
   115  	}
   116  	for i, t := range testInterceptors {
   117  		if u.Host == t.FromHost && (t.Scheme == "" || u.Scheme == t.Scheme) {
   118  			return &testInterceptors[i], true
   119  		}
   120  	}
   121  	return nil, false
   122  }
   123  
   124  func interceptRequest(req *http.Request) {
   125  	if t, ok := interceptURL(req.URL); ok {
   126  		req.Host = req.URL.Host
   127  		req.URL.Host = t.ToHost
   128  	}
   129  }
   130  
   131  func get(security SecurityMode, url *urlpkg.URL) (*Response, error) {
   132  	start := time.Now()
   133  
   134  	if url.Scheme == "file" {
   135  		return getFile(url)
   136  	}
   137  
   138  	if enableTestHooks {
   139  		switch url.Host {
   140  		case "proxy.golang.org":
   141  			if os.Getenv("TESTGOPROXY404") == "1" {
   142  				res := &Response{
   143  					URL:        url.Redacted(),
   144  					Status:     "404 testing",
   145  					StatusCode: 404,
   146  					Header:     make(map[string][]string),
   147  					Body:       http.NoBody,
   148  				}
   149  				if cfg.BuildX {
   150  					fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", url.Redacted(), res.Status, time.Since(start).Seconds())
   151  				}
   152  				return res, nil
   153  			}
   154  
   155  		case "localhost.localdev":
   156  			return nil, fmt.Errorf("no such host localhost.localdev")
   157  
   158  		default:
   159  			if os.Getenv("TESTGONETWORK") == "panic" {
   160  				if _, ok := interceptURL(url); !ok {
   161  					host := url.Host
   162  					if h, _, err := net.SplitHostPort(url.Host); err == nil && h != "" {
   163  						host = h
   164  					}
   165  					addr := net.ParseIP(host)
   166  					if addr == nil || (!addr.IsLoopback() && !addr.IsUnspecified()) {
   167  						panic("use of network: " + url.String())
   168  					}
   169  				}
   170  			}
   171  		}
   172  	}
   173  
   174  	fetch := func(url *urlpkg.URL) (*urlpkg.URL, *http.Response, error) {
   175  		// Note: The -v build flag does not mean "print logging information",
   176  		// despite its historical misuse for this in GOPATH-based go get.
   177  		// We print extra logging in -x mode instead, which traces what
   178  		// commands are executed.
   179  		if cfg.BuildX {
   180  			fmt.Fprintf(os.Stderr, "# get %s\n", url.Redacted())
   181  		}
   182  
   183  		req, err := http.NewRequest("GET", url.String(), nil)
   184  		if err != nil {
   185  			return nil, nil, err
   186  		}
   187  		if url.Scheme == "https" {
   188  			auth.AddCredentials(req)
   189  		}
   190  		t, intercepted := interceptURL(req.URL)
   191  		if intercepted {
   192  			req.Host = req.URL.Host
   193  			req.URL.Host = t.ToHost
   194  		}
   195  
   196  		var res *http.Response
   197  		if security == Insecure && url.Scheme == "https" { // fail earlier
   198  			res, err = impatientInsecureHTTPClient.Do(req)
   199  		} else {
   200  			if intercepted && t.Client != nil {
   201  				client := securityPreservingHTTPClient(t.Client)
   202  				res, err = client.Do(req)
   203  			} else {
   204  				res, err = securityPreservingDefaultClient.Do(req)
   205  			}
   206  		}
   207  		return url, res, err
   208  	}
   209  
   210  	var (
   211  		fetched *urlpkg.URL
   212  		res     *http.Response
   213  		err     error
   214  	)
   215  	if url.Scheme == "" || url.Scheme == "https" {
   216  		secure := new(urlpkg.URL)
   217  		*secure = *url
   218  		secure.Scheme = "https"
   219  
   220  		fetched, res, err = fetch(secure)
   221  		if err != nil {
   222  			if cfg.BuildX {
   223  				fmt.Fprintf(os.Stderr, "# get %s: %v\n", secure.Redacted(), err)
   224  			}
   225  			if security != Insecure || url.Scheme == "https" {
   226  				// HTTPS failed, and we can't fall back to plain HTTP.
   227  				// Report the error from the HTTPS attempt.
   228  				return nil, err
   229  			}
   230  		}
   231  	}
   232  
   233  	if res == nil {
   234  		switch url.Scheme {
   235  		case "http":
   236  			if security == SecureOnly {
   237  				if cfg.BuildX {
   238  					fmt.Fprintf(os.Stderr, "# get %s: insecure\n", url.Redacted())
   239  				}
   240  				return nil, fmt.Errorf("insecure URL: %s", url.Redacted())
   241  			}
   242  		case "":
   243  			if security != Insecure {
   244  				panic("should have returned after HTTPS failure")
   245  			}
   246  		default:
   247  			if cfg.BuildX {
   248  				fmt.Fprintf(os.Stderr, "# get %s: unsupported\n", url.Redacted())
   249  			}
   250  			return nil, fmt.Errorf("unsupported scheme: %s", url.Redacted())
   251  		}
   252  
   253  		insecure := new(urlpkg.URL)
   254  		*insecure = *url
   255  		insecure.Scheme = "http"
   256  		if insecure.User != nil && security != Insecure {
   257  			if cfg.BuildX {
   258  				fmt.Fprintf(os.Stderr, "# get %s: insecure credentials\n", insecure.Redacted())
   259  			}
   260  			return nil, fmt.Errorf("refusing to pass credentials to insecure URL: %s", insecure.Redacted())
   261  		}
   262  
   263  		fetched, res, err = fetch(insecure)
   264  		if err != nil {
   265  			if cfg.BuildX {
   266  				fmt.Fprintf(os.Stderr, "# get %s: %v\n", insecure.Redacted(), err)
   267  			}
   268  			// HTTP failed, and we already tried HTTPS if applicable.
   269  			// Report the error from the HTTP attempt.
   270  			return nil, err
   271  		}
   272  	}
   273  
   274  	// Note: accepting a non-200 OK here, so people can serve a
   275  	// meta import in their http 404 page.
   276  	if cfg.BuildX {
   277  		fmt.Fprintf(os.Stderr, "# get %s: %v (%.3fs)\n", fetched.Redacted(), res.Status, time.Since(start).Seconds())
   278  	}
   279  
   280  	r := &Response{
   281  		URL:        fetched.Redacted(),
   282  		Status:     res.Status,
   283  		StatusCode: res.StatusCode,
   284  		Header:     map[string][]string(res.Header),
   285  		Body:       res.Body,
   286  	}
   287  
   288  	if res.StatusCode != http.StatusOK {
   289  		contentType := res.Header.Get("Content-Type")
   290  		if mediaType, params, _ := mime.ParseMediaType(contentType); mediaType == "text/plain" {
   291  			switch charset := strings.ToLower(params["charset"]); charset {
   292  			case "us-ascii", "utf-8", "":
   293  				// Body claims to be plain text in UTF-8 or a subset thereof.
   294  				// Try to extract a useful error message from it.
   295  				r.errorDetail.r = res.Body
   296  				r.Body = &r.errorDetail
   297  			}
   298  		}
   299  	}
   300  
   301  	return r, nil
   302  }
   303  
   304  func getFile(u *urlpkg.URL) (*Response, error) {
   305  	path, err := urlToFilePath(u)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	f, err := os.Open(path)
   310  
   311  	if os.IsNotExist(err) {
   312  		return &Response{
   313  			URL:        u.Redacted(),
   314  			Status:     http.StatusText(http.StatusNotFound),
   315  			StatusCode: http.StatusNotFound,
   316  			Body:       http.NoBody,
   317  			fileErr:    err,
   318  		}, nil
   319  	}
   320  
   321  	if os.IsPermission(err) {
   322  		return &Response{
   323  			URL:        u.Redacted(),
   324  			Status:     http.StatusText(http.StatusForbidden),
   325  			StatusCode: http.StatusForbidden,
   326  			Body:       http.NoBody,
   327  			fileErr:    err,
   328  		}, nil
   329  	}
   330  
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	return &Response{
   336  		URL:        u.Redacted(),
   337  		Status:     http.StatusText(http.StatusOK),
   338  		StatusCode: http.StatusOK,
   339  		Body:       f,
   340  	}, nil
   341  }
   342  
   343  func openBrowser(url string) bool { return browser.Open(url) }
   344  
   345  func isLocalHost(u *urlpkg.URL) bool {
   346  	// VCSTestRepoURL itself is secure, and it may redirect requests to other
   347  	// ports (such as a port serving the "svn" protocol) which should also be
   348  	// considered secure.
   349  	host, _, err := net.SplitHostPort(u.Host)
   350  	if err != nil {
   351  		host = u.Host
   352  	}
   353  	if host == "localhost" {
   354  		return true
   355  	}
   356  	if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
   357  		return true
   358  	}
   359  	return false
   360  }