github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/cloud_controller_connection.go (about)

     1  package cloudcontroller
     2  
     3  import (
     4  	"crypto/x509"
     5  	"io/ioutil"
     6  	"net"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"errors"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    14  	"code.cloudfoundry.org/cli/util"
    15  )
    16  
    17  // Config is for configuring a CloudControllerConnection.
    18  type Config struct {
    19  	DialTimeout       time.Duration
    20  	SkipSSLValidation bool
    21  }
    22  
    23  // CloudControllerConnection represents a connection to the Cloud Controller
    24  // server.
    25  type CloudControllerConnection struct {
    26  	HTTPClient *http.Client
    27  	UserAgent  string
    28  }
    29  
    30  // NewConnection returns a new CloudControllerConnection with provided
    31  // configuration.
    32  func NewConnection(config Config) *CloudControllerConnection {
    33  	tr := &http.Transport{
    34  		TLSClientConfig: util.NewTLSConfig(nil, config.SkipSSLValidation),
    35  		Proxy:           http.ProxyFromEnvironment,
    36  		DialContext: (&net.Dialer{
    37  			KeepAlive: 30 * time.Second,
    38  			Timeout:   config.DialTimeout,
    39  		}).DialContext,
    40  	}
    41  
    42  	return &CloudControllerConnection{
    43  		HTTPClient: &http.Client{Transport: tr},
    44  	}
    45  }
    46  
    47  // Make performs the request and parses the response.
    48  func (connection *CloudControllerConnection) Make(request *Request, passedResponse *Response) error {
    49  	// In case this function is called from a retry, passedResponse may already
    50  	// be populated with a previous response. We reset in case there's an HTTP
    51  	// error and we don't repopulate it in populateResponse.
    52  	passedResponse.reset()
    53  
    54  	response, err := connection.HTTPClient.Do(request.Request)
    55  	if err != nil {
    56  		return connection.processRequestErrors(request.Request, err)
    57  	}
    58  	defer response.Body.Close()
    59  
    60  	return connection.populateResponse(response, passedResponse)
    61  }
    62  
    63  func (*CloudControllerConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error {
    64  	if response.StatusCode == http.StatusNoContent {
    65  		passedResponse.RawResponse = []byte("{}")
    66  	} else {
    67  		rawBytes, err := ioutil.ReadAll(response.Body)
    68  		if err != nil {
    69  			return err
    70  		}
    71  
    72  		passedResponse.RawResponse = rawBytes
    73  	}
    74  
    75  	if response.StatusCode >= 400 {
    76  		return ccerror.RawHTTPStatusError{
    77  			StatusCode:  response.StatusCode,
    78  			RawResponse: passedResponse.RawResponse,
    79  			RequestIDs:  response.Header["X-Vcap-Request-Id"],
    80  		}
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // handleWarnings looks for the "X-Cf-Warnings" header in the cloud controller
    87  // response and URI decodes them. The value can contain multiple warnings that
    88  // are comma separated.
    89  func (*CloudControllerConnection) handleWarnings(response *http.Response) ([]string, error) {
    90  	rawWarnings := response.Header["X-Cf-Warnings"]
    91  
    92  	var warnings []string
    93  	for _, rawWarningsCommaSeparated := range rawWarnings {
    94  		for _, rawWarning := range strings.Split(rawWarningsCommaSeparated, ",") {
    95  			warning, err := url.QueryUnescape(rawWarning)
    96  			if err != nil {
    97  				return nil, err
    98  			}
    99  			warnings = append(warnings, strings.Trim(warning, " "))
   100  		}
   101  	}
   102  
   103  	return warnings, nil
   104  }
   105  
   106  func (connection *CloudControllerConnection) populateResponse(response *http.Response, passedResponse *Response) error {
   107  	passedResponse.HTTPResponse = response
   108  
   109  	warnings, err := connection.handleWarnings(response)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	passedResponse.Warnings = warnings
   114  
   115  	if resourceLocationURL := response.Header.Get("Location"); resourceLocationURL != "" {
   116  		passedResponse.ResourceLocationURL = resourceLocationURL
   117  	}
   118  
   119  	err = connection.handleStatusCodes(response, passedResponse)
   120  	if err != nil {
   121  		return err
   122  	}
   123  
   124  	if passedResponse.DecodeJSONResponseInto != nil {
   125  		err = DecodeJSON(passedResponse.RawResponse, passedResponse.DecodeJSONResponseInto)
   126  		if err != nil {
   127  			return err
   128  		}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  func (*CloudControllerConnection) processRequestErrors(request *http.Request, err error) error {
   135  	switch e := err.(type) {
   136  	case *url.Error:
   137  		if errors.As(err, &x509.UnknownAuthorityError{}) {
   138  			return ccerror.UnverifiedServerError{
   139  				URL: request.URL.String(),
   140  			}
   141  		}
   142  
   143  		hostnameError := x509.HostnameError{}
   144  	        if errors.As(err, &hostnameError) {
   145  			return ccerror.SSLValidationHostnameError{
   146  				Message: hostnameError.Error(),
   147  			}
   148  		}
   149  
   150  		return ccerror.RequestError{Err: e}
   151  
   152  	default:
   153  		return err
   154  	}
   155  }