mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-31 16:02:17 +00:00
301bfffeee
Receivers can echo tokens, alert content or private provider data in a response or body-read error. Ordinary, enhanced and ntfy recovery senders now retain structured HTTP verdicts and bounded byte counts without retaining or formatting that text. Test keeps its existing response shape with an explicit withheld-body summary. Preserve authoritative failure classes, retry hints, wrapped read causes, occurrence/payload identity, request credentials and the existing 1 MiB read cap. Regression controls reproduce disclosure on the parent and cover the real senders, retry history, interrupted replies, cleanup and bounded summaries. No historical diagnostic rewrite or native provider acceptance is claimed. Change-source: pulse-maintainer
61 lines
2.2 KiB
Go
61 lines
2.2 KiB
Go
package notifications
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
// Receivers can echo credentials, alert content or arbitrary provider text.
|
|
// Keep only bounded byte counts and structured delivery facts, not a copy of
|
|
// the body that another caller could put in logs, history or a Test response.
|
|
func readWebhookResponse(resp *http.Response) (*webhookHTTPResult, error) {
|
|
result := &webhookHTTPResult{statusCode: resp.StatusCode, headers: resp.Header.Clone()}
|
|
var err error
|
|
result.responseBytes, err = io.Copy(io.Discard, io.LimitReader(resp.Body, WebhookMaxResponseSize))
|
|
result.responseLimitReached = result.responseBytes == WebhookMaxResponseSize
|
|
result.responseIncomplete = err != nil
|
|
if err != nil {
|
|
// Preserve identity and classification before hiding the error prose.
|
|
// A body-read error can itself contain the receiver's private text.
|
|
class := ClassifyNotificationFailureError(err)
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
class = ClassFromHTTPStatus(resp.StatusCode)
|
|
}
|
|
return result, FailWithClass(class, &webhookResponseReadError{status: resp.StatusCode, cause: err})
|
|
}
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return result, FailfWithClass(ClassFromHTTPStatus(resp.StatusCode),
|
|
"webhook returned HTTP %d: %s (response body withheld)", resp.StatusCode, http.StatusText(resp.StatusCode))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
type webhookResponseReadError struct {
|
|
status int
|
|
cause error
|
|
}
|
|
|
|
func (e *webhookResponseReadError) Error() string {
|
|
if e.status < 200 || e.status >= 300 {
|
|
return fmt.Sprintf("webhook returned HTTP %d: failed to read response body (details withheld)", e.status)
|
|
}
|
|
return "failed to read webhook response body (details withheld)"
|
|
}
|
|
|
|
func (e *webhookResponseReadError) Unwrap() error { return e.cause }
|
|
|
|
func (r *webhookHTTPResult) responseSummary() string {
|
|
if r.responseBytes == 0 && !r.responseIncomplete {
|
|
return ""
|
|
}
|
|
suffix := ""
|
|
if r.responseIncomplete {
|
|
suffix = "; read incomplete"
|
|
} else if r.responseLimitReached {
|
|
// Reaching the cap is not proof that the body exceeded it.
|
|
suffix = "; read limit reached"
|
|
}
|
|
return fmt.Sprintf("Response body withheld (%d bytes read%s)", r.responseBytes, suffix)
|
|
}
|