Skip to content

fix(dart): derive InputFile filename from path - #1902

Open
abnegate wants to merge 4 commits into
mainfrom
fix/chunked-upload-filename
Open

abnegate wants to merge 4 commits into
mainfrom
fix/chunked-upload-filename

Conversation

@abnegate

Copy link
Copy Markdown
Member

Summary

Uploads over 5 MB from the Flutter and Dart SDKs fail with storage_file_empty / "Empty file passed to the endpoint." whenever the caller uses InputFile.fromPath(path: ...) without passing filename:. Files at or under 5 MB upload fine, which is what makes it look like a chunking problem.

It isn't. The SDK chunks correctly. The two upload paths just build the multipart part differently:

Size Path content-disposition sent
≤ 5 MB MultipartFile.fromPath(...) name="file"; filename="video.mp4"
> 5 MB MultipartFile.fromBytes(..., filename: file.filename) name="file"

InputFile.fromPath leaves filename null when the caller omits it. package:http's IO helper does filename ??= segments.last, so the single-request path always ends up with a filename, while fromBytes passes the null straight through and package:http emits the attribute only if (file.filename != null).

A multipart part with no filename is not a file upload. Swoole puts it in $request->post, so $request->getFiles('file') returns [] and Create.php throws STORAGE_FILE_EMPTY. Every chunk is rejected.

Fix

Derive the filename from the path at construction, the same way package:http already does for MultipartFile.fromPath, so both upload paths send an identical part. Dart and Flutter share this template, so one change covers both SDKs. A path with no final segment (/path/to/) still leaves filename null, so nothing starts sending filename="".

Verification

Driving the real generated SDK at a Swoole server that reproduces Appwrite's getFiles('file') check, uploading 12 MB via InputFile.fromPath(path: ...) with no filename:

Before:

RESULT: upload FAILED -> storage_file_empty: Empty file passed to the endpoint.
server: CHUNK REJECTED: no file part (landed in post: fileId,file)

After:

progress 42%  chunk 1/3
progress 83%  chunk 2/3
progress 100% chunk 3/3
RESULT: upload SUCCEEDED, name="holiday-video.mp4"
server: CHUNK OK: name=holiday-video.mp4 size=5242880 range=bytes 0-5242879/12582912
        CHUNK OK: name=holiday-video.mp4 size=5242880 range=bytes 5242880-10485759/12582912
        CHUNK OK: name=holiday-video.mp4 size=2097152 range=bytes 10485760-12582911/12582912

The unit tests were also seen red against the pre-fix template, failing on exactly the null filename:

InputFile creates InputFile from path [E]                        Expected: 'file'      Actual: <null>
InputFile derives filename from path when none is given [E]      Expected: 'video.mp4' Actual: <null>
InputFile derives filename from path on the deprecated ctor [E]  Expected: 'video.mp4' Actual: <null>

Generation suite: 87 passed, 4 skipped. lint-twig: 586 files, 0 errors. Generated Dart SDK passes dart analyze with no issues.

Notes

  • The existing test asserted expect(inputFile.filename, isNull), which pinned the broken behaviour. It is updated rather than removed.
  • Flutter Web is unaffected: InputFile.fromBytes already requires a filename.
  • Present since 2022-06-14, so shipped SDKs need a release to pick this up. Until then the workaround is to pass filename: explicitly.
  • There is no E2E test covering the chunked upload path in this repo; the verification above was run by hand against a real Swoole parser.
  • composer lint (phpcs) reports 219 pre-existing errors across 21 PHP files on main. This PR changes no PHP and leaves them alone.

🤖 Generated with Claude Code

Uploads over 5MB switch from MultipartFile.fromPath to fromBytes, and
only fromPath derives a filename when the caller omits one. Every chunk
part therefore went out with no filename in its content-disposition, so
Swoole parsed it as an ordinary form field instead of a file upload,
getFiles('file') came back empty and the server rejected the upload with
storage_file_empty.

Deriving the filename at construction keeps both upload paths sending
the same part. Dart and Flutter share this template, so both are fixed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; the observable multipart regression is covered and no outstanding correctness issue remains.

Summary

This PR derives a Dart InputFile filename from its path so chunked multipart uploads include the required filename attribute, and adds generated-SDK coverage of that behavior.

  • Preserves explicitly supplied filenames and leaves paths without a final segment unresolved.
  • Registers a Dart multipart integration test that exercises more than one chunk.
  • Revises the chunk test to avoid asserting an exact chunk count while checking every received part.

Reviews (3) · Last reviewed commit: "test(dart): assert chunk filenames witho..."

Comment thread templates/dart/test/src/input_file_test.dart.twig
abnegate and others added 2 commits September 22, 2026 13:14
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The InputFile tests only asserted the derived filename field. This
uploads a file one byte over the chunk size to a loopback HttpServer and
checks every chunk's content-disposition carries the filename, so the
regression (chunks sent as plain form fields, rejected as an empty file)
fails the suite even if chunkedUpload stops forwarding the field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread templates/dart/test/src/client_io_test.dart.twig
The stub now derives completion from the content-range header the client
sends instead of reporting a fixed chunksTotal, and the assertion is that
every multipart request carried the filename rather than that exactly two
did, so a change to ClientIO.chunkSize cannot turn this red on its own.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant