fix: address review findings from reviews #2955 and #2958

- 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:
claw
2026-05-12 21:19:01 -07:00
parent b380e7fcae
commit 6316007eb1
3 changed files with 60 additions and 3 deletions
+16 -2
View File
@@ -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 {