fix(http): F-Q-008 add HttpError::InvalidArgs for user-input validation failures
http_service::run mapped `Method::from_bytes` failure on a user-supplied
HTTP method to HttpError::Backend with format!("invalid method: {}", …).
Same for HeaderName/HeaderValue validation in build_headers. But that's
user input, not a backend problem — misclassifying as Backend corrupts
retry policies (operators may retry "backend" but not "invalid input").
Add a new HttpError::InvalidArgs(String) variant. Reroute the three
validation paths in http_service (method, header name, header value) to
emit InvalidArgs instead of Backend.
The executor-side `map_http_err` uses Display, so the new variant
surfaces to scripts as "http: invalid method: …" — same format, more
structured behind it.
AUDIT.md anchor: F-Q-008.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -205,8 +205,9 @@ impl HttpServiceImpl {
|
|||||||
/// Core request path: validate, build headers, follow redirects
|
/// Core request path: validate, build headers, follow redirects
|
||||||
/// manually, read the response body with a cap.
|
/// manually, read the response body with a cap.
|
||||||
async fn run(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
|
async fn run(&self, req: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
// F-Q-008: bad method is user input, not a backend problem.
|
||||||
let method = Method::from_bytes(req.method.as_bytes())
|
let method = Method::from_bytes(req.method.as_bytes())
|
||||||
.map_err(|_| HttpError::Backend(format!("invalid method: {}", req.method)))?;
|
.map_err(|_| HttpError::InvalidArgs(format!("invalid method: {}", req.method)))?;
|
||||||
|
|
||||||
let mut current = url::Url::parse(&req.url)
|
let mut current = url::Url::parse(&req.url)
|
||||||
.map_err(|e| HttpError::InvalidUrl(format!("{}: {e}", req.url)))?;
|
.map_err(|e| HttpError::InvalidUrl(format!("{}: {e}", req.url)))?;
|
||||||
@@ -336,10 +337,11 @@ fn build_headers(req: &HttpRequest, _url: &url::Url) -> Result<HeaderMap, HttpEr
|
|||||||
let mut has_user_agent = false;
|
let mut has_user_agent = false;
|
||||||
let mut has_content_type = false;
|
let mut has_content_type = false;
|
||||||
for (k, v) in &req.headers {
|
for (k, v) in &req.headers {
|
||||||
|
// F-Q-008: bad header name/value is user input.
|
||||||
let name = HeaderName::from_bytes(k.as_bytes())
|
let name = HeaderName::from_bytes(k.as_bytes())
|
||||||
.map_err(|_| HttpError::Backend(format!("invalid header name: {k}")))?;
|
.map_err(|_| HttpError::InvalidArgs(format!("invalid header name: {k}")))?;
|
||||||
let value = HeaderValue::from_str(v)
|
let value = HeaderValue::from_str(v)
|
||||||
.map_err(|_| HttpError::Backend(format!("invalid header value for {k}")))?;
|
.map_err(|_| HttpError::InvalidArgs(format!("invalid header value for {k}")))?;
|
||||||
if name == USER_AGENT {
|
if name == USER_AGENT {
|
||||||
has_user_agent = true;
|
has_user_agent = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ pub enum HttpError {
|
|||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Network(String),
|
Network(String),
|
||||||
|
|
||||||
|
/// F-Q-008: user-supplied input failed validation (bad HTTP method
|
||||||
|
/// name, malformed header name/value). Distinct from `Backend`
|
||||||
|
/// because operators may safely retry a `Backend` error but should
|
||||||
|
/// never retry an `InvalidArgs` failure — the input itself is wrong.
|
||||||
|
#[error("{0}")]
|
||||||
|
InvalidArgs(String),
|
||||||
|
|
||||||
/// Anything else the impl wants to surface (still safe to show a
|
/// Anything else the impl wants to surface (still safe to show a
|
||||||
/// script).
|
/// script).
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
|
|||||||
Reference in New Issue
Block a user