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 }