fix: prevent client-upload crash when image bytes are unavailable#16971
Draft
denolfe wants to merge 1 commit into
Draft
fix: prevent client-upload crash when image bytes are unavailable#16971denolfe wants to merge 1 commit into
denolfe wants to merge 1 commit into
Conversation
A client-upload storage adapter whose staticHandler returns a 302 redirect (empty body) crashed the upload with a 400: core fetched the body back into a 0-byte buffer, then getImageSize called image-size on it, throwing "RangeError: Offset is outside the bounds of the DataView". Fix A (crash guard): getImageSize now returns ProbedImageSize | undefined, guarding empty/missing buffers and catching image-size errors so a corrupt or empty body degrades to undefined dimensions instead of failing the upload. Fix B (skip the fetch-back): the client measures naturalWidth/naturalHeight and sends them in the client-upload JSON. uploadRequiresFileData determines whether the server actually needs the bytes (sharp adjustments, animated types, imageSizes, or crop edits — focalPoint alone does not). When dimensions are supplied and no server-side work is needed, addDataAndFileToRequest skips the staticHandler fetch-back entirely, so bytes never enter the server for no-resize collections. generateFileData falls back to client dimensions when probing yields nothing.
Contributor
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Fixes a 400 crash when uploading an image through a client-upload storage adapter whose
staticHandlerreturns a 302 redirect, and lets collections that don't resize record image dimensions without pulling the file bytes back into the server.During a client upload the file body never transits the server, so core fetches it back from the adapter to probe dimensions. A redirect (empty body) produced a 0-byte buffer that made
image-sizethrowRangeError: Offset is outside the bounds of the DataView.Key Changes
Guard dimension probing against missing or unreadable bytes
getImageSizenow returnsundefinedfor empty/missing buffers and catchesimage-sizefailures, so a redirect or corrupt body degrades to undefined dimensions instead of failing the upload.generateFileDatatolerates the undefined result.Send client-measured dimensions with the upload
The browser measures the image's natural width and height and includes them in the client-upload payload.
generateFileDatafalls back to these when byte probing yields nothing, so dimensions are still recorded for redirect-only adapters.Skip the byte fetch-back when the server needs no bytes
uploadRequiresFileDatadecides whether server-side work (sharp adjustments, animated types,imageSizes, crop edits) needs the file. When client dimensions are present and no such work applies,addDataAndFileToRequestskips thestaticHandlerfetch-back, so bytes never enter the server for collections that don't resize.Design Decisions
Hardening
getImageSizeis the minimal, broadly protective change: no adapter can crash the upload pipeline by returning an empty body. It stands on its own regardless of the dimension-passing work.The decision of whether bytes are needed lives in core rather than the adapter, because only core knows the collection's resize configuration.
focalPointalone does not require bytes, sincecreateImageSizesreturns early withoutimageSizes. The skip path is conservative: when bytes might be needed, it falls back to the existing fetch-back, so correctness never depends on guessing.ProbedImageSizekeeps its required numbers; onlygetImageSize's return type widens to| undefined. The resize machinery always runs with real bytes, so it stays untouched.Overall Flow
sequenceDiagram participant Browser participant Core as addDataAndFileToRequest participant Adapter as staticHandler participant Gen as generateFileData Browser->>Browser: measure naturalWidth/Height Browser->>Core: multipart with client-upload JSON (incl. width/height) alt dimensions present and no server-side work needed Core->>Gen: req.file with empty buffer + dimensions Note over Core,Adapter: fetch-back skipped, bytes stay in storage else bytes needed (imageSizes / crop / adjustments / animated) Core->>Adapter: fetch file back Adapter-->>Core: bytes (or redirect, yielding an empty buffer) Core->>Gen: req.file with bytes, dimensions carried through end Gen->>Gen: probe bytes, fall back to client dimensions if unreadable