github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/plugin/plugin_connection.go (about) 1 package plugin 2 3 import ( 4 "bytes" 5 "crypto/x509" 6 "encoding/json" 7 "io" 8 "io/ioutil" 9 "net" 10 "net/http" 11 "net/url" 12 "time" 13 "errors" 14 15 "code.cloudfoundry.org/cli/api/plugin/pluginerror" 16 "code.cloudfoundry.org/cli/util" 17 ) 18 19 // PluginConnection represents a connection to a plugin repo. 20 type PluginConnection struct { 21 HTTPClient *http.Client 22 proxyReader ProxyReader // nolint 23 } 24 25 // NewConnection returns a new PluginConnection 26 func NewConnection(skipSSLValidation bool, dialTimeout time.Duration) *PluginConnection { 27 tr := &http.Transport{ 28 TLSClientConfig: util.NewTLSConfig(nil, skipSSLValidation), 29 Proxy: http.ProxyFromEnvironment, 30 DialContext: (&net.Dialer{ 31 KeepAlive: 30 * time.Second, 32 Timeout: dialTimeout, 33 }).DialContext, 34 } 35 36 return &PluginConnection{ 37 HTTPClient: &http.Client{Transport: tr}, 38 } 39 } 40 41 // Make performs the request and parses the response. 42 func (connection *PluginConnection) Make(request *http.Request, passedResponse *Response, proxyReader ProxyReader) error { 43 // In case this function is called from a retry, passedResponse may already 44 // be populated with a previous response. We reset in case there's an HTTP 45 // error and we don't repopulate it in populateResponse. 46 passedResponse.reset() 47 48 response, err := connection.HTTPClient.Do(request) 49 if err != nil { 50 return connection.processRequestErrors(request, err) 51 } 52 53 body := response.Body 54 if proxyReader != nil { 55 proxyReader.Start(response.ContentLength) 56 defer proxyReader.Finish() 57 body = proxyReader.Wrap(response.Body) 58 } 59 60 return connection.populateResponse(response, passedResponse, body) 61 } 62 63 func (*PluginConnection) handleStatusCodes(response *http.Response, passedResponse *Response) error { 64 if response.StatusCode >= 400 { 65 return pluginerror.RawHTTPStatusError{ 66 Status: response.Status, 67 RawResponse: passedResponse.RawResponse, 68 } 69 } 70 71 return nil 72 } 73 74 func (connection *PluginConnection) populateResponse(response *http.Response, passedResponse *Response, body io.ReadCloser) error { 75 passedResponse.HTTPResponse = response 76 77 rawBytes, err := ioutil.ReadAll(body) 78 defer body.Close() 79 if err != nil { 80 return err 81 } 82 passedResponse.RawResponse = rawBytes 83 84 err = connection.handleStatusCodes(response, passedResponse) 85 if err != nil { 86 return err 87 } 88 89 if passedResponse.Result != nil { 90 decoder := json.NewDecoder(bytes.NewBuffer(passedResponse.RawResponse)) 91 decoder.UseNumber() 92 err = decoder.Decode(passedResponse.Result) 93 if err != nil { 94 return err 95 } 96 } 97 98 return nil 99 } 100 101 // processRequestError handles errors that occur while making the request. 102 func (connection *PluginConnection) processRequestErrors(request *http.Request, err error) error { 103 switch e := err.(type) { 104 case *url.Error: 105 if errors.As(err, &x509.UnknownAuthorityError{}) { 106 return pluginerror.UnverifiedServerError{ 107 URL: request.URL.String(), 108 } 109 } 110 111 hostnameError := x509.HostnameError{} 112 if errors.As(err, &hostnameError) { 113 return pluginerror.SSLValidationHostnameError{ 114 Message: hostnameError.Error(), 115 } 116 } 117 118 return pluginerror.RequestError{Err: e} 119 120 default: 121 return err 122 } 123 }