[img] missing r2 libs

This commit is contained in:
Devin Zuczek
2026-08-10 14:10:02 -04:00
parent 793b2ad37a
commit 30cf83a47d
2 changed files with 51 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
/**
* Serving R2 objects to clients that ask for byte ranges.
*
* Shared because getting it wrong is silent: a downloader that asked for a slice writes
* whatever comes back at the offset it asked for, so answering a `Range` request with a
* whole-object 200 corrupts the reassembled file rather than failing it. Both `cdn` and
* `img` stream R2 objects, so both need the same rule.
*
* Pass the request's headers straight to `get(key, { range: c.req.raw.headers })`: R2
* parses the `Range` header itself and resolves every form (`bytes=a-b`, `bytes=a-`,
* `bytes=-n`) to a concrete offset/length, so there is no grammar to reimplement — and a
* hand-rolled parser is exactly where the whole-object 200 crept in, because the values it
* failed to parse became "no range at all". Anything R2 cannot parse or satisfy resolves
* to the whole object; `contentRange` is what keeps that honest.
*/
/**
* Write `Content-Range` and `Content-Length` onto the response headers for a body R2 has
* returned, and report whether the response must therefore be a 206.
*
* Returns true for any `bytes=` request that produced a resolved range — INCLUDING one
* covering the whole object. R2 answers a multi-range or unsatisfiable value with every
* byte, and the safe way to hand that back is a 206 stating which bytes are enclosed: a
* client can act on `bytes 0-5/6`, but a bare 200 tells it nothing and it assumes the body
* is the slice it asked for.
*
* Returns false — writing nothing — when the request named a unit other than `bytes` (RFC
* 9110 says an unrecognised unit must be ignored, and answering it in bytes would be its
* own lie), when no `Range` was sent, or when R2 returned no range.
*/
export function writeContentRange(
headers: Headers,
requestHeaders: Headers,
object: R2Object
): boolean {
if (!requestHeaders.get('range')?.startsWith('bytes=')) return false
// R2 hands back the RESOLVED range, and the object carries all three keys with the
// inapplicable ones set to undefined — so `'suffix' in r` is true even for an
// offset/length range and cannot discriminate between the two forms. (It read as a
// suffix range every time, making offset/length NaN and the header garbage.) Read the
// values, not the keys. A `bytes=-N` request already comes back resolved to a concrete
// offset/length; the suffix fallback is only there in case that ever stops being true.
const r = object.range as { offset?: number; length?: number; suffix?: number } | undefined
if (!r) return false
const length = r.length ?? r.suffix ?? object.size - (r.offset ?? 0)
const offset = r.offset ?? object.size - length
headers.set('content-length', String(length))
headers.set('content-range', `bytes ${offset}-${offset + length - 1}/${object.size}`)
return true
}
+1
View File
@@ -4,6 +4,7 @@ export { logger } from './helpers/logger'
export { getRequestLogData, type LogDataRequest } from './helpers/request'
export * from './helpers/errors'
export * from './helpers/openapi'
export * from './helpers/r2'
export * from './helpers/url'
export * from './middleware/withCache'
export * from './middleware/withDefaultCors'