fix: treat empty file inputs as no upload on add-to-cart - #2
Merged
Merged
Conversation
The controller guard counted entries in the request's file collection and read a non-empty collection as proof that something had been uploaded. It is not. PHP puts every file input on a submitted multipart form into $_FILES, including ones the customer left empty, with error UPLOAD_ERR_NO_FILE and a zero size, and Laminas' mapPhpFiles keeps all of them when it converts the superglobal — nothing filters them out. The product view form carries enctype="multipart/form-data" whenever the product has any option at all, not only a file one (module-catalog form.phtml). So with cart_add_file off, any product offering an optional file option returned 403 on an ordinary purchase where the customer simply left the upload empty. The 403 was returned before the backstop could apply its own no-upload handling, so the earlier fix one layer down did not help here. The guard now walks the mapped entries and counts one only when it carries a real upload: an entry whose error is anything other than UPLOAD_ERR_NO_FILE. A file PHP rejected for exceeding the size limit still counts, because a file was still sent. The walk recurses, since Laminas nests entries when an input name carries brackets. Audited the other guards for the same assumption. The customer upload guard is unaffected: both the open-source and Commerce controllers check for an empty file collection before constructing the uploader, and neither sits on a purchase path. The REST guard reads option structure rather than files. The backstop uses core's own isUploaded(), which rejects an entry with an empty name, so it was already correct. 43 tests, 73 assertions, all green against the 2.4.8-p5 tree. New coverage: four shapes that must not be treated as uploads, including nested and multi-option submissions, and four that must, including a mixed submission and a file rejected for size.
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.
Follow-up to #1, which merged before this fix was pushed. The branch commit landed on
feature/surfaceguard-v1after the merge, so this fix is not onmain—maincurrently carries the bug described below.The bug
[P1] With
cart_add_fileswitched off, an ordinary purchase returns 403 whenever the product has an optional file option the customer left empty.The controller guard counted entries in the request's file collection and read a non-empty collection as proof of an upload. It is not:
PHP puts every file input on a submitted multipart form into
$_FILES, including ones left empty, witherror = UPLOAD_ERR_NO_FILEandsize = 0.Laminas'
mapPhpFiles()(laminas-http/src/PhpEnvironment/Request.php:408) copies every$_FILESentry through to the request's file parameters. Nothing filters the empty ones — I grepped the framework andmodule-catalogforUPLOAD_ERR_NO_FILEand there is no such filtering anywhere.The product view form sets
enctype="multipart/form-data"whenever the product has any option, not only a file one:(
module-catalog/view/frontend/templates/product/view/form.phtml:21)So the 403 fired on a normal add-to-cart, and it fired at the controller — before the backstop's no-upload handling from #1 could run. This is the same bug class as finding 1 on that PR, surviving one layer up.
The fix
The guard now walks the mapped entries and counts one only when it carries a real upload: an entry whose
erroris anything other thanUPLOAD_ERR_NO_FILE. A file PHP rejected for exceeding the size limit still counts, because a file was still sent and refusing it is the intended behaviour. The walk recurses, because Laminas nests entries when an input name carries brackets (mapPhpFileParam).Audit of the other guards
Checked each one for the same assumption:
DenyCartAddFileDenyCustomerFileUploadAddress\File\Uploadand the CommerceAbstractUploadFilecheckempty($requestedFiles)before constructing the uploader, and neither sits on a purchase path.DenyCartItemFileOptionDenyFileOptionBackstopisUploaded(), which returns false whenempty($file['name'])— exactly the empty-input case.Verification
php -lclean.phpcs --standard=Magento2not run (not installed in the available vendor tree), andsetup:di:compilestill not run against a live install.