github.com/DaAlbrecht/cf-cli@v0.0.0-20231128151943-1fe19bb400b9/cf/net/http_client.go (about)

     1  package net
     2  
     3  import (
     4  	_ "crypto/sha512" // #82254112: http://bridge.grumpy-troll.org/2014/05/golang-tls-comodo/
     5  	"crypto/x509"
     6  	"fmt"
     7  	"net"
     8  	"net/http"
     9  	"net/url"
    10  	"strings"
    11  	asErrors "errors"
    12  
    13  	"code.cloudfoundry.org/cli/cf/errors"
    14  	. "code.cloudfoundry.org/cli/cf/i18n"
    15  	"golang.org/x/net/websocket"
    16  )
    17  
    18  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . HTTPClientInterface
    19  
    20  type HTTPClientInterface interface {
    21  	RequestDumperInterface
    22  
    23  	Do(*http.Request) (*http.Response, error)
    24  	ExecuteCheckRedirect(req *http.Request, via []*http.Request) error
    25  }
    26  
    27  type client struct {
    28  	*http.Client
    29  	dumper RequestDumper
    30  }
    31  
    32  var NewHTTPClient = func(tr *http.Transport, dumper RequestDumper) HTTPClientInterface {
    33  	c := client{
    34  		&http.Client{
    35  			Transport: tr,
    36  		},
    37  		dumper,
    38  	}
    39  	c.CheckRedirect = c.checkRedirect
    40  
    41  	return &c
    42  }
    43  
    44  func (cl *client) ExecuteCheckRedirect(req *http.Request, via []*http.Request) error {
    45  	return cl.CheckRedirect(req, via)
    46  }
    47  
    48  func (cl *client) checkRedirect(req *http.Request, via []*http.Request) error {
    49  	if len(via) > 1 {
    50  		return errors.New(T("stopped after 1 redirect"))
    51  	}
    52  
    53  	prevReq := via[len(via)-1]
    54  	cl.copyHeaders(prevReq, req, getBaseDomain(req.URL.String()) == getBaseDomain(via[0].URL.String()))
    55  	cl.dumper.DumpRequest(req)
    56  
    57  	return nil
    58  }
    59  
    60  func (cl *client) copyHeaders(from *http.Request, to *http.Request, sameDomain bool) {
    61  	for key, values := range from.Header {
    62  		// do not copy POST-specific headers
    63  		if key != "Content-Type" && key != "Content-Length" && !(!sameDomain && key == "Authorization") {
    64  			to.Header.Set(key, strings.Join(values, ","))
    65  		}
    66  	}
    67  }
    68  
    69  func (cl *client) DumpRequest(req *http.Request) {
    70  	cl.dumper.DumpRequest(req)
    71  }
    72  
    73  func (cl *client) DumpResponse(res *http.Response) {
    74  	cl.dumper.DumpResponse(res)
    75  }
    76  
    77  func WrapNetworkErrors(host string, err error) error {
    78  	var innerErr error
    79  	switch typedErr := err.(type) {
    80  	case *url.Error:
    81  		innerErr = typedErr.Err
    82  	case *websocket.DialError:
    83  		innerErr = typedErr.Err
    84  	}
    85  
    86  	if innerErr != nil {
    87  		if asErrors.As(innerErr, &x509.UnknownAuthorityError{}){
    88  			return errors.NewInvalidSSLCert(host, T("unknown authority"))
    89  		}
    90  		if asErrors.As(innerErr, &x509.HostnameError{}){
    91  			return errors.NewInvalidSSLCert(host, T("not valid for the requested host"))
    92  		}
    93  		if asErrors.As(innerErr, &x509.CertificateInvalidError{}){
    94  			return errors.NewInvalidSSLCert(host, "")
    95  		}
    96  		typedInnerErr := new(net.OpError)
    97  		if asErrors.As(innerErr, &typedInnerErr) {
    98  			if typedInnerErr.Op == "dial" {
    99  				return fmt.Errorf("%s: %s\n%s", T("Error performing request"), err.Error(), T("TIP: If you are behind a firewall and require an HTTP proxy, verify the https_proxy environment variable is correctly set. Else, check your network connection."))
   100  			}
   101  		}
   102  	}
   103  
   104  	return fmt.Errorf("%s: %s", T("Error performing request"), err.Error())
   105  }
   106  
   107  func getBaseDomain(host string) string {
   108  	hostURL, _ := url.Parse(host)
   109  	hostStrs := strings.Split(hostURL.Host, ".")
   110  	return hostStrs[len(hostStrs)-2] + "." + hostStrs[len(hostStrs)-1]
   111  }