-
-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Description
When using getObjectRaw(key, wholeFile, rangeFrom, rangeTo) with rangeTo set to undefined for open-ended HTTP range requests (e.g., bytes=8388608-), s3mini generates an invalid Range header that causes S3 to return a 400 - InvalidArgument error.
Environment
- s3mini version: 0.7.1
- S3 Provider: RustFS (S3-compatible)
Steps to Reproduce
- Call
getObjectRaw()with explicitrangeFrombutundefinedrangeTo:
const s3client = new S3mini({...});
// Attempt to fetch from byte 8388608 to end of file
const response = await s3client.getObjectRaw(
"video.mp4",
false, // wholeFile = false
8388608, // rangeFrom = 8388608
undefined // rangeTo = undefined (should mean "to end of file")
);- S3 responds with error:
<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>InvalidArgument</Code>
<Message>invalid header: range: "bytes=8388608-8388607"</Message>
</Error>Expected Behavior
When rangeTo is undefined, s3mini should generate an open-ended range header:
Range: bytes=8388608-
This is a standard HTTP range request meaning "from byte 8388608 to the end of the file."
Actual Behavior
s3mini generates an invalid range header where the end byte is LESS than the start byte:
Range: bytes=8388608-8388607
This causes S3 to reject the request with 400 - InvalidArgument.
Root Cause
Found in the getObjectRaw() method (lines 1019-1032 in the source):
public async getObjectRaw(
key: string,
wholeFile = true,
rangeFrom = 0,
rangeTo = this.requestSizeInBytes, // ← Default value problem
opts: Record<string, unknown> = {},
ssecHeaders?: IT.SSECHeaders,
): Promise<Response> {
const rangeHdr: Record<string, string | number> = wholeFile
? {}
: { range: `bytes=${rangeFrom}-${rangeTo - 1}` }; // ← Always subtracts 1
// ...
}The Problem:
- The
rangeToparameter has a default value ofthis.requestSizeInBytes(8388608 bytes = 8MB) - When you call
getObjectRaw(key, false, 8388608, undefined), JavaScript/TypeScript doesn't passundefinedas the default value—it uses the parameter's default:this.requestSizeInBytes - This results in
rangeTo = 8388608whenrangeFrom = 8388608 - The range header becomes:
bytes=8388608-${8388608-1}=bytes=8388608-8388607 - This is invalid because end byte < start byte
The function has no way to detect open-ended ranges because undefined gets replaced by the default value before the function body executes.
Proposed Fix
Change the function signature to make rangeTo optional without a default value:
public async getObjectRaw(
key: string,
wholeFile = true,
rangeFrom = 0,
rangeTo?: number, // ← Remove default value, make optional
opts: Record<string, unknown> = {},
ssecHeaders?: IT.SSECHeaders,
): Promise<Response> {
let rangeHdr: Record<string, string | number> = {};
if (!wholeFile) {
if (rangeTo !== undefined) {
// Closed range with explicit end
rangeHdr = { range: `bytes=${rangeFrom}-${rangeTo - 1}` };
} else {
// Open-ended range: from rangeFrom to end of file
rangeHdr = { range: `bytes=${rangeFrom}-` };
}
}
return this._signedRequest('GET', key, {
query: { ...opts },
headers: { ...rangeHdr, ...ssecHeaders },
withQuery: true,
});
}Why this works:
- Removes the default value from
rangeTo, making it truly optional - Explicitly checks for
undefinedto create open-ended ranges - Maintains backward compatibility for callers who provide explicit values
- Fixes the invalid range generation bug
Workaround
Currently, users must fetch the file size first and provide it explicitly:
// 1. Get file size via small range request
const sizeCheck = await s3client.getObjectRaw(key, false, 0, 1);
const contentRange = sizeCheck.headers.get("content-range");
const fileSize = parseInt(contentRange.match(/bytes \d+-\d+\/(\d+)/)[1]);
// 2. Now fetch with explicit end byte
const response = await s3client.getObjectRaw(
key,
false,
8388608,
fileSize // Must provide explicit file size instead of undefined
);Use Case
This bug affects video streaming implementations where browsers make open-ended range requests as users watch videos. A typical video playback session generates requests like:
GET /video.mp4→Range: bytes=0-- (user seeks to 30 seconds)
GET /video.mp4→Range: bytes=8388608-
Without support for open-ended ranges, video seeking breaks.
Additional Context
The HTTP/1.1 specification (RFC 7233) defines open-ended range requests:
A client can limit the number of bytes requested without knowing the size of the selected representation. If the last-byte-pos value is absent, or if the value is greater than or equal to the current length of the representation data, the byte range is interpreted as the remainder of the representation (i.e., the server replaces the value of last-byte-pos with a value that is one less than the current length of the selected representation).
Source: https://datatracker.ietf.org/doc/html/rfc7233#section-2.1
S3-compatible storage services follow this standard and expect open-ended ranges to work.