- Convert handleResponse to package-level function (unused receiver) [#17955] - Add clarifying comment for nil resp on transport error [#17956] - Use consistent %w wrapping in dual-unmarshal error path [#17957] - Add SafeError() method to APIError for safe logging [#17964] - Enforce safe CheckRedirect policy in SetHTTPClient [#17965] - Add tests for SafeError and SetHTTPClient enforcement
This commit is contained in:
+16
-2
@@ -47,6 +47,13 @@ func (e *APIError) Error() string {
|
||||
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, body)
|
||||
}
|
||||
|
||||
// SafeError returns the error string without response body content,
|
||||
// suitable for logging in contexts where upstream response data should
|
||||
// not be exposed.
|
||||
func (e *APIError) SafeError() string {
|
||||
return fmt.Sprintf("HTTP %d", e.StatusCode)
|
||||
}
|
||||
|
||||
// IsNotFound reports whether an error is an API 404 response.
|
||||
func IsNotFound(err error) bool {
|
||||
if apiErr, ok := asAPIError(err); ok {
|
||||
@@ -172,6 +179,12 @@ func (c *Client) SetHTTPClient(hc *http.Client) {
|
||||
Timeout: 30 * time.Second,
|
||||
CheckRedirect: defaultCheckRedirect,
|
||||
}
|
||||
} else if hc.CheckRedirect == nil {
|
||||
// Enforce safe redirect policy when caller provides a client without one.
|
||||
// The default net/http behavior follows up to 10 redirects and forwards
|
||||
// all headers (including Authorization) to any host, which can leak
|
||||
// credentials on cross-host redirects.
|
||||
hc.CheckRedirect = defaultCheckRedirect
|
||||
}
|
||||
c.httpClient = hc
|
||||
}
|
||||
@@ -252,10 +265,11 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
// Transport errors (DNS, TLS, timeout) yield nil resp; no body to close.
|
||||
return nil, fmt.Errorf("do request: %w", err)
|
||||
}
|
||||
|
||||
body, done, err := c.handleResponse(resp, maxResponseBytes, maxErrorBodyBytes)
|
||||
body, done, err := handleResponse(resp, maxResponseBytes, maxErrorBodyBytes)
|
||||
if done {
|
||||
return body, err
|
||||
}
|
||||
@@ -300,7 +314,7 @@ func (c *Client) doRequest(ctx context.Context, method, reqURL string, accept st
|
||||
// handleResponse reads and closes the response body, returning the result.
|
||||
// It uses defer to ensure the body is always closed regardless of code path.
|
||||
// Returns (body, done, err) where done=true means the caller should return immediately.
|
||||
func (c *Client) handleResponse(resp *http.Response, maxRespBytes int, maxErrBytes int) ([]byte, bool, error) {
|
||||
func handleResponse(resp *http.Response, maxRespBytes int, maxErrBytes int) ([]byte, bool, error) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
|
||||
|
||||
Reference in New Issue
Block a user