From 5fa96a4561f1731c3ff2973431a7b159877bcd0e Mon Sep 17 00:00:00 2001 From: Juraj Kirchheim Date: Fri, 23 Oct 2026 22:58:46 +0100 Subject: [PATCH] Blob.slice: resolve an unknown file size before counting a negative index from the end A Bun.file() with an unknown size counted negative start/end from MAX_SIZE. Co-Authored-By: Claude Sonnet 5.5 --- src/runtime/webcore/Blob.rs | 23 ++++++++++++++++------- test/js/bun/util/bun-file.test.ts | 13 +++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/runtime/webcore/Blob.rs b/src/runtime/webcore/Blob.rs index d985e71cef97..107686d891c2 100644 --- a/src/runtime/webcore/Blob.rs +++ b/src/runtime/webcore/Blob.rs @@ -1756,13 +1756,6 @@ impl BlobExt for Blob { return Ok(unsafe { BlobExt::to_js(&*ptr, global_this) }); } - let size_i64 = i64::try_from(self.size.get()).unwrap_or(i64::MAX); - - // If the optional start parameter is not used as a parameter, let relativeStart be 0. - let mut relative_start: i64 = 0; - // If the optional end parameter is not used, let relativeEnd be size. - let mut relative_end: i64 = size_i64; - // A string in place of `start` or `end` is the content type. let (start, end, content_type) = match (start.to_js(), end.to_js(), content_type.to_js()) { (start, _, _) if start.is_string() => (JSValue::UNDEFINED, JSValue::UNDEFINED, start), @@ -1770,6 +1763,22 @@ impl BlobExt for Blob { args => args, }; + // A negative `start` or `end` counts from the end, so a file whose size is not known yet + // has to be stat()ed first (otherwise it would count from `MAX_SIZE`). + if self.size.get() == MAX_SIZE + && ((start.is_number() && start.to_int64() < 0) + || (end.is_number() && end.to_int64() < 0)) + { + self.resolve_size(); + } + + let size_i64 = i64::try_from(self.size.get()).unwrap_or(i64::MAX); + + // If the optional start parameter is not used as a parameter, let relativeStart be 0. + let mut relative_start: i64 = 0; + // If the optional end parameter is not used, let relativeEnd be size. + let mut relative_end: i64 = size_i64; + if start.is_number() { let start = start.to_int64(); if start < 0 { diff --git a/test/js/bun/util/bun-file.test.ts b/test/js/bun/util/bun-file.test.ts index 129d83df99da..50f701ecec26 100644 --- a/test/js/bun/util/bun-file.test.ts +++ b/test/js/bun/util/bun-file.test.ts @@ -198,3 +198,16 @@ test.concurrent.each([ expect(Number(live)).toBeWithin(1, 50); expect(exitCode).toBe(0); }); + +test("a negative slice of a Bun.file whose size is not known yet counts from the end of the file", async () => { + using dir = tempDir("bun-file-negative-slice", { "a.txt": "hello world\n" }); + const path = join(String(dir), "a.txt"); + // Each Bun.file() starts with an unknown size. + expect(await Bun.file(path).slice(-5).text()).toBe("orld\n"); + expect(await Bun.file(path).slice(-5, -1).text()).toBe("orld"); + expect(await Bun.file(path).slice(0, -6).text()).toBe("hello"); + expect(await Bun.file(path).slice(-100).text()).toBe("hello world\n"); + const file = Bun.file(path); + expect(file.size).toBe(12); + expect(await file.slice(-5).text()).toBe("orld\n"); +});