Skip to content

Commit 3a6d356

Browse files
fix: ignore the Range header on non-GET requests in the axum extractor
RFC 9110 Section 14.2 defines range handling for the GET method only and requires servers to ignore the Range header received with any other method. The extractor previously extracted a range regardless of the request method. Return `None` unless the method is GET, and correct the doc comment that claimed servers "must" ignore malformed Range headers: the spec says they may ignore or reject them.
1 parent b7d5647 commit 3a6d356

2 files changed

Lines changed: 63 additions & 5 deletions

File tree

src/headers/range.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,22 @@ where
9191

9292
/// Extracts an optional [`HttpRange`] from the request's `Range` header.
9393
///
94-
/// Per [RFC 9110 Section 14.2], a server that receives a `Range` header it
95-
/// cannot parse or does not support (unknown range unit, multiple ranges,
96-
/// malformed values) **must** ignore the header and serve the full
97-
/// representation. This extractor returns `Ok(None)` in all such cases
98-
/// instead of rejecting the request.
94+
/// Per [RFC 9110 Section 14.2], range handling is only defined for the
95+
/// GET method, and a server may ignore a `Range` header it cannot parse
96+
/// or does not support (unknown range unit, multiple ranges, malformed
97+
/// values). This extractor returns `Ok(None)` for non-GET requests and
98+
/// in all such cases instead of rejecting the request, so that the full
99+
/// representation is served.
99100
///
100101
/// [RFC 9110 Section 14.2]: https://www.rfc-editor.org/rfc/rfc9110#section-14.2
101102
async fn from_request_parts(
102103
parts: &mut http::request::Parts,
103104
_state: &S,
104105
) -> Result<Option<Self>, Self::Rejection> {
106+
if parts.method != http::Method::GET {
107+
return Ok(None);
108+
}
109+
105110
let range = parts
106111
.headers
107112
.get(http::header::RANGE)

src/headers/tests.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,59 @@ mod serve_file {
692692
}
693693
}
694694

695+
#[cfg(feature = "axum")]
696+
mod axum_range_extractor {
697+
use std::{
698+
pin::pin,
699+
task::{Context, Poll, Waker},
700+
};
701+
702+
use axum_core::extract::OptionalFromRequestParts;
703+
use http::{Method, Request, header::RANGE};
704+
705+
use crate::headers::{OrderedRange, range::HttpRange};
706+
707+
fn extract(method: Method, range: Option<&str>) -> Option<HttpRange> {
708+
let mut builder = Request::builder().method(method);
709+
if let Some(range) = range {
710+
builder = builder.header(RANGE, range);
711+
}
712+
let (mut parts, ()) = builder.body(()).unwrap().into_parts();
713+
714+
let fut =
715+
pin!(<HttpRange as OptionalFromRequestParts<()>>::from_request_parts(&mut parts, &()));
716+
match fut.poll(&mut Context::from_waker(Waker::noop())) {
717+
Poll::Ready(Ok(range)) => range,
718+
Poll::Ready(Err(infallible)) => match infallible {},
719+
Poll::Pending => unreachable!("the extractor never awaits"),
720+
}
721+
}
722+
723+
#[test]
724+
fn get_request_extracts_range() {
725+
assert_eq!(
726+
extract(Method::GET, Some("bytes=0-10")),
727+
Some(HttpRange::Range(OrderedRange::new(0..=10).unwrap()))
728+
);
729+
}
730+
731+
#[test]
732+
fn non_get_request_ignores_range() {
733+
assert_eq!(extract(Method::POST, Some("bytes=0-10")), None);
734+
assert_eq!(extract(Method::PUT, Some("bytes=0-10")), None);
735+
}
736+
737+
#[test]
738+
fn missing_range_extracts_none() {
739+
assert_eq!(extract(Method::GET, None), None);
740+
}
741+
742+
#[test]
743+
fn malformed_range_is_ignored() {
744+
assert_eq!(extract(Method::GET, Some("bytes=10-0")), None);
745+
}
746+
}
747+
695748
#[cfg(test)]
696749
mod if_range {
697750
use http::HeaderValue;

0 commit comments

Comments
 (0)