laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.56k stars 11.03k forks source link

[5.0] File upload is broken when submitting a form without selecting a file #6189

Closed Marwelln closed 10 years ago

Marwelln commented 10 years ago

After the recent changes (I did composer update a couple of minutes ago) you can't submit a form if you have enctype="multipart/form-data" and input[type="file"] and leaves the input field empty (no file selected).

Link to error message.

Fatal error: Uncaught exception 'InvalidArgumentException' with message 'An uploaded file must be an array or an instance of UploadedFile.' in /var/www/foobar/public/liferaft/file-upload-fails/vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php on line 59

Reproduce

1.

composer create-project laravel/laravel . dev-develop

2.

php artisan clear-compiled

3.

Set debug to true in config/app.php.

4.

Open up app/Providers/RouteServiceProvider.php and change the map method to this:

$router->group(['namespace' => 'App\Http\Controllers'], function() use ($router) {
    $router->get('/', 'HomeController@index');
    $router->put('/', 'HomeController@put');
});

5.

Add the following code to app/Http/HomeController.php:

public function put() {
    dd(\Input::all());
}

6.

Change resources/views/hello.php to this:

<!doctype html>
<html lang="en">
<body>
    <form method='post' enctype='multipart/form-data'>
        <input type='hidden' name='_method' value='put' />
        <input type='hidden' name='_token' value='<?= csrf_token(); ?>' />

        <input type='file' name='image' />
        <button>Send</button>
    </form>
</body>
</html>

If you try to submit the form without selecting a file you will get Uncaught exception 'InvalidArgumentException' with message 'An uploaded file must be an array or an instance of UploadedFile.', but if you select an image, you will get an array from Input::all().

Bad (temporary) solution

A solution is to remove an file arrays with a null value before using the SymfonyRequest::dublicate method.

Change the content of the createFromBase method from

if ($request instanceof static) return $request;

return (new static)->duplicate(

    $request->query->all(), $request->request->all(), $request->attributes->all(),

    $request->cookies->all(), $request->files->all(), $request->server->all()
);

to

if ($request instanceof static) return $request;

$files = [];
foreach ($request->files->all() as $index => $file) {
    if (null !== $file) $files[$index] = $file;
}

return (new static)->duplicate(

    $request->query->all(), $request->request->all(), $request->attributes->all(),

    $request->cookies->all(), $files, $request->server->all()
);

You can now submit the form without selecting a file.

youanden commented 10 years ago

Update - check out @mantasradzevicius post of @lucasmichot 's fix below. It's more holistic and isn't based on javascript or editing core files.

+1

I've been having trouble with laravel-stapler after manually migrating and setting it up. Same result if using a multipart form type.

My solution is hilarious but doesn't require editing core files if you're okay with alienating non-js users:

$(document).ready ->
  $('.profile-form').on 'submit', (e) ->
    $photoInput = $('.profile-photo-input input')
    $photoInput.remove() if $photoInput.val() == ""
    return true

Essentially, I remove the input if the value hasn't been set to anything. Super hacky but it might fit for someone in the interim.

mikedugan commented 10 years ago

+1

jeffberry commented 10 years ago

+1

Hoping for a fix soon.

etcinit commented 10 years ago

I was about to create an issue for this too. I wrote a test case for it:

In HttpRequestTest.php:

 public function testInputWithEmptyFilename()
    {
        $invalidFiles = [
            'file' => [
                'name' => null,
                'type' => null,
                'tmp_name' => null,
                'error' => 4,
                'size' => 0
            ]
        ];

        $baseRequest = \Symfony\Component\HttpFoundation\Request::create('/?boom=breeze', 'GET', array('foo' => array('bar' => 'baz')), array(), $invalidFiles);

        $request = Request::createFromBase($baseRequest);
    }
leothelocust commented 10 years ago

+1

jmfleming commented 10 years ago
MathieuDoyon commented 10 years ago

+1

mouhsinelonly commented 10 years ago

+1

davidnknight commented 10 years ago

+1

wlkns commented 10 years ago

+1

alexandre-leites commented 10 years ago

+1

jufy commented 10 years ago

+1

alexleonard commented 10 years ago

+1

ingria commented 10 years ago

+1

alexleonard commented 10 years ago

Thanks @youanden for the temporary JS fix. Works a treat.

youanden commented 10 years ago

@alexleonard glad I could be of service.

colinyoung87 commented 10 years ago

@youanden I used your fix, did the trick until a fix came out, thanks!

I've rewritten it a little with jquery to work across all file forms:

$('form[enctype="multipart/form-data"]').on('submit', function(e){
    $(this).find('input[type=file]').each(function(){
        var file = $(this);
        if (file.val() == "") file.remove();
    });
    return true;
});
lucidlemon commented 10 years ago

+1 and +1 for marking it as important

gregoryduckworth commented 10 years ago

+1

mn7z commented 10 years ago

As a temp solution how to apply this fix:

Example


namespace App\Http;

use Illuminate\Http\Request as LaravelRequest;

class Request extends LaravelRequest
{

    /**
     * {@inheritdoc}
     */
    public function duplicate(
        array $query = null,
        array $request = null,
        array $attributes = null,
        array $cookies = null,
        array $files = null,
        array $server = null
    ) {
        $files = array_filter((array)$files);

        return parent::duplicate(
            $query, 
            $request, 
            $attributes, 
            $cookies, 
            $files, 
            $server
        );
    }
}
$response = $kernel->handle(
    $request = App\Http\Request::capture()
);
$response->send();

Does this work: yes SideEffects: not known

markvdputten commented 10 years ago

@colinyoung87 your temproary jQuery solution works perfect, thanks for that!

youanden commented 10 years ago

@mantasradzevicius Thanks. I can confirm this worked after changing the namespace accordingly.

gorkhali125 commented 10 years ago

@colinyoung87 Thanks for the solution. This worked perfectly.

jonathanpmartins commented 10 years ago

Perhaps it can be a Symfony problem. what do you think ?

Fatal error: Uncaught exception 'InvalidArgumentException' with message 'An uploaded file must be an array or an instance of UploadedFile.' in /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php:59 Stack trace: #0 /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php(73): Symfony\Component\HttpFoundation\FileBag->set('image', NULL) #1 /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php(48): Symfony\Component\HttpFoundation\FileBag->add(Array) #2 /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php(37): Symfony\Component\HttpFoundation\FileBag->replace(Array) #3 /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Request.php(448): Symfony\Component\HttpFoundation\FileBag->__construct(Array) #4 /var/www/html/.../vendor/laravel/framework/src/Illuminate/Http/Reque in /var/www/html/.../vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/FileBag.php on line 59

FileBag->set('image', NULL), receaves NULL, and that is what is dispatching a throw!

...in Symfony on the FileBag.php file, convertFileInformation method returns NULL if no files was selected on the upload.

sfunaro commented 10 years ago

+1 @colinyoung87 temporary solution works perfectly!

pomirleanu commented 9 years ago

+1

FranciscoCaldeira commented 3 years ago

I have this problem using the multipartGraphQL() when testing the upload of a file! +1