@@ -1310,15 +1310,6 @@ pub mod range_filter {
13101310 return RangeType :: None ;
13111311 }
13121312
1313- // "A server MUST ignore a Range header field received with a request method other than GET."
1314- if req. method != http:: Method :: GET && req. method != http:: Method :: HEAD {
1315- return RangeType :: None ;
1316- }
1317-
1318- let Some ( range_header) = req. headers . get ( RANGE ) else {
1319- return RangeType :: None ;
1320- } ;
1321-
13221313 // Content-Length is not required by RFC but it is what nginx does and easier to implement
13231314 // with this header present.
13241315 let Some ( content_length_bytes) = resp. headers . get ( CONTENT_LENGTH ) else {
@@ -1329,41 +1320,65 @@ pub mod range_filter {
13291320 return RangeType :: None ;
13301321 } ;
13311322
1332- // if-range wants to understand if the Last-Modified / ETag value matches exactly for use
1333- // with resumable downloads.
1334- // https://datatracker.ietf.org/doc/html/rfc9110#name-if-range
1335- // Note that the RFC wants strong validation, and suggests that
1336- // "A valid entity-tag can be distinguished from a valid HTTP-date
1337- // by examining the first three characters for a DQUOTE,"
1338- // but this current etag matching behavior most closely mirrors nginx.
1339- if let Some ( if_range) = req. headers . get ( IF_RANGE ) {
1340- let ir = if_range. as_bytes ( ) ;
1341- let matches = if ir. len ( ) >= 2 && ir. last ( ) == Some ( & b'"' ) {
1342- resp. headers . get ( ETAG ) . is_some_and ( |etag| etag == if_range)
1343- } else if let Some ( last_modified) = resp. headers . get ( LAST_MODIFIED ) {
1344- last_modified == if_range
1345- } else {
1346- false
1347- } ;
1348- if !matches {
1323+ // At this point the response is allowed to be served as ranges
1324+ // TODO: we can also check Accept-Range header from resp. Nginx gives uses the option
1325+ // see proxy_force_ranges
1326+
1327+ fn request_range_type (
1328+ req : & RequestHeader ,
1329+ resp : & ResponseHeader ,
1330+ content_length : usize ,
1331+ max_multipart_ranges : Option < usize > ,
1332+ ) -> RangeType {
1333+ // "A server MUST ignore a Range header field received with a request method other than GET."
1334+ if req. method != http:: Method :: GET && req. method != http:: Method :: HEAD {
13491335 return RangeType :: None ;
13501336 }
1351- }
13521337
1353- // TODO: we can also check Accept-Range header from resp. Nginx gives uses the option
1354- // see proxy_force_ranges
1338+ let Some ( range_header) = req. headers . get ( RANGE ) else {
1339+ return RangeType :: None ;
1340+ } ;
13551341
1356- let mut range_type = parse_range_header (
1357- range_header. as_bytes ( ) ,
1358- content_length,
1359- max_multipart_ranges,
1360- ) ;
1342+ // if-range wants to understand if the Last-Modified / ETag value matches exactly for use
1343+ // with resumable downloads.
1344+ // https://datatracker.ietf.org/doc/html/rfc9110#name-if-range
1345+ // Note that the RFC wants strong validation, and suggests that
1346+ // "A valid entity-tag can be distinguished from a valid HTTP-date
1347+ // by examining the first three characters for a DQUOTE,"
1348+ // but this current etag matching behavior most closely mirrors nginx.
1349+ if let Some ( if_range) = req. headers . get ( IF_RANGE ) {
1350+ let ir = if_range. as_bytes ( ) ;
1351+ let matches = if ir. len ( ) >= 2 && ir. last ( ) == Some ( & b'"' ) {
1352+ resp. headers . get ( ETAG ) . is_some_and ( |etag| etag == if_range)
1353+ } else if let Some ( last_modified) = resp. headers . get ( LAST_MODIFIED ) {
1354+ last_modified == if_range
1355+ } else {
1356+ false
1357+ } ;
1358+ if !matches {
1359+ return RangeType :: None ;
1360+ }
1361+ }
1362+
1363+ parse_range_header (
1364+ range_header. as_bytes ( ) ,
1365+ content_length,
1366+ max_multipart_ranges,
1367+ )
1368+ }
1369+
1370+ let mut range_type = request_range_type ( req, resp, content_length, max_multipart_ranges) ;
13611371
13621372 match & mut range_type {
1363- RangeType :: None => { /* nothing to do*/ }
1373+ RangeType :: None => {
1374+ // At this point, the response is _eligible_ to be served in ranges
1375+ // in the future, so add Accept-Ranges, mirroring nginx behavior
1376+ resp. insert_header ( & ACCEPT_RANGES , "bytes" ) . unwrap ( ) ;
1377+ }
13641378 RangeType :: Single ( r) => {
13651379 // 206 response
13661380 resp. set_status ( StatusCode :: PARTIAL_CONTENT ) . unwrap ( ) ;
1381+ resp. remove_header ( & ACCEPT_RANGES ) ;
13671382 resp. insert_header ( & CONTENT_LENGTH , r. end - r. start )
13681383 . unwrap ( ) ;
13691384 resp. insert_header (
@@ -1386,6 +1401,7 @@ pub mod range_filter {
13861401 let total_length = multi_range_info. calculate_multipart_length ( ) ;
13871402
13881403 resp. set_status ( StatusCode :: PARTIAL_CONTENT ) . unwrap ( ) ;
1404+ resp. remove_header ( & ACCEPT_RANGES ) ;
13891405 resp. insert_header ( CONTENT_LENGTH , total_length) . unwrap ( ) ;
13901406 resp. insert_header (
13911407 CONTENT_TYPE ,
@@ -1403,6 +1419,7 @@ pub mod range_filter {
14031419 // empty body for simplicity
14041420 resp. insert_header ( & CONTENT_LENGTH , HeaderValue :: from_static ( "0" ) )
14051421 . unwrap ( ) ;
1422+ resp. remove_header ( & ACCEPT_RANGES ) ;
14061423 // TODO: remove other headers like content-encoding
14071424 resp. remove_header ( & CONTENT_TYPE ) ;
14081425 resp. insert_header ( & CONTENT_RANGE , format ! ( "bytes */{content_length}" ) )
@@ -1429,6 +1446,21 @@ pub mod range_filter {
14291446 let mut resp = gen_resp ( ) ;
14301447 assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
14311448 assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1449+ assert_eq ! (
1450+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1451+ b"bytes"
1452+ ) ;
1453+
1454+ // no range, try HEAD
1455+ let mut req = gen_req ( ) ;
1456+ req. method = Method :: HEAD ;
1457+ let mut resp = gen_resp ( ) ;
1458+ assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
1459+ assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1460+ assert_eq ! (
1461+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1462+ b"bytes"
1463+ ) ;
14321464
14331465 // regular range
14341466 let mut req = gen_req ( ) ;
@@ -1444,11 +1476,31 @@ pub mod range_filter {
14441476 resp. headers. get( "content-range" ) . unwrap( ) . as_bytes( ) ,
14451477 b"bytes 0-1/10"
14461478 ) ;
1479+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
1480+
1481+ // regular range, accept-ranges included
1482+ let mut req = gen_req ( ) ;
1483+ req. insert_header ( "Range" , "bytes=0-1" ) . unwrap ( ) ;
1484+ let mut resp = gen_resp ( ) ;
1485+ resp. insert_header ( "Accept-Ranges" , "bytes" ) . unwrap ( ) ;
1486+ assert_eq ! (
1487+ RangeType :: new_single( 0 , 2 ) ,
1488+ range_header_filter( & req, & mut resp, None )
1489+ ) ;
1490+ assert_eq ! ( resp. status. as_u16( ) , 206 ) ;
1491+ assert_eq ! ( resp. headers. get( "content-length" ) . unwrap( ) . as_bytes( ) , b"2" ) ;
1492+ assert_eq ! (
1493+ resp. headers. get( "content-range" ) . unwrap( ) . as_bytes( ) ,
1494+ b"bytes 0-1/10"
1495+ ) ;
1496+ // accept-ranges stripped
1497+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
14471498
14481499 // bad range
14491500 let mut req = gen_req ( ) ;
14501501 req. insert_header ( "Range" , "bytes=1-0" ) . unwrap ( ) ;
14511502 let mut resp = gen_resp ( ) ;
1503+ resp. insert_header ( "Accept-Ranges" , "bytes" ) . unwrap ( ) ;
14521504 assert_eq ! (
14531505 RangeType :: Invalid ,
14541506 range_header_filter( & req, & mut resp, None )
@@ -1459,6 +1511,7 @@ pub mod range_filter {
14591511 resp. headers. get( "content-range" ) . unwrap( ) . as_bytes( ) ,
14601512 b"bytes */10"
14611513 ) ;
1514+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
14621515 }
14631516
14641517 // Multipart Tests
@@ -1507,6 +1560,7 @@ pub mod range_filter {
15071560 format!( "multipart/byteranges; boundary={boundary_str}" )
15081561 ) ;
15091562 assert ! ( resp. headers. get( "content_length" ) . is_none( ) ) ;
1563+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
15101564
15111565 // overlapping range, multipart range is declined
15121566 let req = gen_req_overlap_range ( ) ;
@@ -1516,6 +1570,10 @@ pub mod range_filter {
15161570 assert ! ( matches!( result, RangeType :: None ) ) ;
15171571 assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
15181572 assert ! ( resp. headers. get( "content-type" ) . is_none( ) ) ;
1573+ assert_eq ! (
1574+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1575+ b"bytes"
1576+ ) ;
15191577
15201578 // bad multipart range
15211579 let mut req = gen_req ( ) ;
@@ -1525,6 +1583,7 @@ pub mod range_filter {
15251583 let result = range_header_filter ( & req, & mut resp, None ) ;
15261584 assert ! ( matches!( result, RangeType :: Invalid ) ) ;
15271585 assert_eq ! ( resp. status. as_u16( ) , 416 ) ;
1586+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
15281587 }
15291588
15301589 #[ test]
@@ -1565,6 +1624,11 @@ pub mod range_filter {
15651624 . unwrap ( ) ;
15661625 let mut resp = gen_resp ( ) ;
15671626 assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
1627+ assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1628+ assert_eq ! (
1629+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1630+ b"bytes"
1631+ ) ;
15681632
15691633 // match ETag
15701634 let mut req = gen_req ( ) ;
@@ -1574,17 +1638,29 @@ pub mod range_filter {
15741638 RangeType :: new_single( 0 , 2 ) ,
15751639 range_header_filter( & req, & mut resp, None )
15761640 ) ;
1641+ assert_eq ! ( resp. status. as_u16( ) , 206 ) ;
1642+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
15771643
15781644 // non-matching ETags do not result in range
15791645 let mut req = gen_req ( ) ;
15801646 req. insert_header ( "If-Range" , "\" 4567\" " ) . unwrap ( ) ;
15811647 let mut resp = gen_resp ( ) ;
15821648 assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
1649+ assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1650+ assert_eq ! (
1651+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1652+ b"bytes"
1653+ ) ;
15831654
15841655 let mut req = gen_req ( ) ;
15851656 req. insert_header ( "If-Range" , "1234" ) . unwrap ( ) ;
15861657 let mut resp = gen_resp ( ) ;
15871658 assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
1659+ assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1660+ assert_eq ! (
1661+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1662+ b"bytes"
1663+ ) ;
15881664
15891665 // multipart range with If-Range
15901666 let mut req = get_multipart_req ( ) ;
@@ -1593,6 +1669,7 @@ pub mod range_filter {
15931669 let result = range_header_filter ( & req, & mut resp, None ) ;
15941670 assert ! ( matches!( result, RangeType :: Multi ( _) ) ) ;
15951671 assert_eq ! ( resp. status. as_u16( ) , 206 ) ;
1672+ assert ! ( resp. headers. get( "accept-ranges" ) . is_none( ) ) ;
15961673
15971674 // multipart with matching ETag
15981675 let req = get_multipart_req ( ) ;
@@ -1608,6 +1685,10 @@ pub mod range_filter {
16081685 let mut resp = gen_resp ( ) ;
16091686 assert_eq ! ( RangeType :: None , range_header_filter( & req, & mut resp, None ) ) ;
16101687 assert_eq ! ( resp. status. as_u16( ) , 200 ) ;
1688+ assert_eq ! (
1689+ resp. headers. get( "accept-ranges" ) . unwrap( ) . as_bytes( ) ,
1690+ b"bytes"
1691+ ) ;
16111692 }
16121693
16131694 pub struct RangeBodyFilter {
0 commit comments