clawhdf5-remote: a 200 covering the requested range is the whole file

The first request asks for bytes=0-1048575. RFC 9110 lets a server answer
200 when the range covers the whole representation, so a file under
1 MiB on a server that does support ranges could be refused as 'does not
support range requests'. A 200 whose Content-Length (or, without one, its
body, read at most that far) is within the range asked for is now kept as
the whole file and read from memory; a longer one is still refused unless
allow_full_download is set.

Test: a 9968-byte file served with 200 opens in one request with the
transcript of File::open (it was refused before); with a 4096-byte first
request it is still refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-09-26 18:32:15 -05:00
co-authored by Claude Opus 5.5
parent 680c90b3a8
commit 61e34927dc
2 changed files with 50 additions and 5 deletions
+27 -5
View File
@@ -12,6 +12,9 @@
//! open is [`RemoteError::FileChanged`], never a mix of old and new bytes.
//! (A server that sends neither validator cannot be checked beyond the
//! length; [`HttpOptions::require_validator`] refuses such servers.)
//! - a `200` answer to the first request whose body is no longer than the
//! range asked for is the whole file (a server may answer so when the
//! range covers it): it is kept and read from memory.
//! - a server that ignores `Range` and answers `200` with the whole file is
//! refused with [`RemoteError::RangeNotSupported`], unless
//! [`HttpOptions::allow_full_download`] is set: then the file is
@@ -508,15 +511,34 @@ impl HttpStorage {
Ok((total, validator, bytes, false))
}
200 => {
if !self.options.allow_full_download {
return Err(RemoteError::RangeNotSupported(format!(
// RFC 9110 lets a server answer 200 when the range covers
// the whole file: a body no longer than the range asked
// for is the whole file, ranges supported or not.
let want: Option<u64> =
header(&resp, "content-length").and_then(|v| v.trim().parse().ok());
let refused = || {
RemoteError::RangeNotSupported(format!(
"{} answered a range request with the whole file (status 200); set \
HttpOptions::allow_full_download to download it",
self.redactor.shown()
)));
))
};
if !self.options.allow_full_download {
return match want {
Some(w) if w <= n => {
let bytes = self.body(resp, Some(w), 0)?;
Ok((bytes.len() as u64, validator, bytes, true))
}
Some(_) => Err(refused()),
// No length: read at most the range asked for.
None => match self.body(resp, None, n) {
Ok(bytes) => Ok((bytes.len() as u64, validator, bytes, true)),
Err(RemoteError::Usage(_)) => Err(refused()),
Err(e) => Err(e),
},
};
}
let want = header(&resp, "content-length").and_then(|v| v.trim().parse().ok());
if want.is_some_and(|w: u64| w > self.options.max_full_download) {
if want.is_some_and(|w| w > self.options.max_full_download) {
return Err(RemoteError::Usage(format!(
"{}: the file is larger than HttpOptions::max_full_download ({} bytes)",
self.redactor.shown(),