mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-29 08:27:06 +00:00
fix(rustfs): fail GetObject stream on short body instead of truncating (#4594)
This commit is contained in:
@@ -888,6 +888,17 @@ impl<R: AsyncRead + Unpin> AsyncRead for GetObjectStreamingReader<R> {
|
|||||||
"GetObject streaming body ended before expected length"
|
"GetObject streaming body ended before expected length"
|
||||||
);
|
);
|
||||||
self.finish_err();
|
self.finish_err();
|
||||||
|
// The inner reader signalled a clean EOF before delivering the full
|
||||||
|
// Content-Length. Returning Ok here would hand the client a truncated body
|
||||||
|
// under a full Content-Length: the peer treats the short body as complete
|
||||||
|
// (e.g. `mc mirror` writes a short file and considers it done — the
|
||||||
|
// "incomplete data mirroring" in issue #2955). Surface an error instead so
|
||||||
|
// the transfer fails loudly and the client retries rather than persisting
|
||||||
|
// truncated data.
|
||||||
|
return Poll::Ready(Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
|
"get object streaming body ended before the expected content length",
|
||||||
|
)));
|
||||||
} else {
|
} else {
|
||||||
self.completed = true;
|
self.completed = true;
|
||||||
self.finish_ok();
|
self.finish_ok();
|
||||||
@@ -7209,6 +7220,39 @@ mod tests {
|
|||||||
assert_eq!(GetObjectGuard::concurrent_count(), initial);
|
assert_eq!(GetObjectGuard::concurrent_count(), initial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial_test::serial]
|
||||||
|
async fn get_object_streaming_reader_errors_on_short_eof() {
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
// The inner reader delivers 5 bytes then a clean EOF, but the advertised
|
||||||
|
// Content-Length is 10. The reader must surface an error rather than a clean EOF, so
|
||||||
|
// the client sees a failed transfer instead of silently persisting a truncated body
|
||||||
|
// (the "incomplete data mirroring" of #2955).
|
||||||
|
let initial = GetObjectGuard::concurrent_count();
|
||||||
|
let guard = GetObjectGuard::new();
|
||||||
|
assert_eq!(GetObjectGuard::concurrent_count(), initial + 1);
|
||||||
|
|
||||||
|
let mut reader = GetObjectStreamingReader::new(
|
||||||
|
std::io::Cursor::new(b"short".to_vec()),
|
||||||
|
"test-bucket",
|
||||||
|
"truncated-object",
|
||||||
|
10,
|
||||||
|
Duration::ZERO,
|
||||||
|
GetObjectBodyLifecycle::tracked(guard),
|
||||||
|
);
|
||||||
|
let mut out = Vec::new();
|
||||||
|
let err = reader
|
||||||
|
.read_to_end(&mut out)
|
||||||
|
.await
|
||||||
|
.expect_err("short body under a larger Content-Length must fail the stream");
|
||||||
|
assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof);
|
||||||
|
assert_eq!(out, b"short", "bytes read before the short EOF are still delivered");
|
||||||
|
|
||||||
|
drop(reader);
|
||||||
|
assert_eq!(GetObjectGuard::concurrent_count(), initial);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[serial_test::serial]
|
#[serial_test::serial]
|
||||||
fn get_object_streaming_reader_releases_request_guard_when_dropped_incomplete() {
|
fn get_object_streaming_reader_releases_request_guard_when_dropped_incomplete() {
|
||||||
|
|||||||
Reference in New Issue
Block a user