t0berius's avatar

laravel5.1 size validator doesn't work on fileupload

In my controller I want to set a maximum filesize and some allowed mime types a user can upload. I tried the max and size rule, none of them seems to work, I can set them to "1" and I can still upload files. The MIME types seems to be ignored. For example I can still try to upload the git setup (29MB and a .exe file)!

controller:

public function postTicket(Request $request)
{

    $validator = Validator::make($request->all(), [
    'departments' => 'exists:departments,id',
    'attachment' =>'min:1|max:1000|mimes:txt,text,log',
    
    ]);

    if ($validator->fails()) 
       return back()->withErrors($validator)->withInput();
    

    if ($request->hasFile('attachment')) 
    {
        if ($request->file('attachment')->isValid()) 
        {

           $request->file('attachment')->move('../storage/app/uploads', 'lala.txt');

            dd("uploaded");

            
        }

        dd("upload failed");
    }

    dd("no file");
}

Where's my fault I mean the validator rule doesn't check the git setup for example, with it's 29MB it should fail the size check and the MIME type exe isn't allowed too. It passes the check and all I see is:

Warning: POST Content-Length of 30617046 bytes exceeds the limit of 4194304 bytes in Unknown on line 0

"no file"
0 likes
15 replies
skliche's avatar

@jaheller The file is larger than what is allowed by your PHP configuration so PHP rejects it. It never makes it to your Laravel application code. That's why there is no file. Try a much smaller file and you'll see that the MIME check works just fine.

t0berius's avatar

Any way to avoid so users don't see this PHP error?

t0berius's avatar

@skliche I've tried to implement dropzone.js. My view:

<html>
<head> 

<script src="js/dropzone.js"></script>

</head> 
<body>
@if($errors->has())
   @foreach ($errors->all() as $error)
      <div>{{ $error }}</div>
  @endforeach
@endif

<form method="POST" action="{{ action('supportController@postTicket') }}" enctype="multipart/form-data"  class="dropzone">

    <div>
        Titel:
        <input type="text" maxlength="50" name="title" value="{{ old('title') }}">
    </div>

    <div>
        Frage:
        <textarea name="question">
            {{ old('question') }}
        </textarea>
    </div>

    <div>
        Abteilung:

        <select name="departments">
            @foreach ($departments as $department)
                <option value="{{ $department->id }}">{{ $department->name }}</option>
            @endforeach
            <option value="3">MÜLL</option>
            
        </select>
    </div>

    Attachment:
    <div id="my-awesome-dropzone">
    </div>

    <div>
        <button type="submit">Absenden</button>
    </div>
</form>
</body>

</html>

Includes are done correct, web console doesn't show an error. I've done all as http://www.dropzonejs.com/#usage told me to do. Any idea? I hate js.

skliche's avatar

@jaheller When you are using dropzone you should use different forms for your main stuff and the uploads. You can combine both but just keep them separate for the time being.

routes.php

// uploaded files will get posted to this route
Route::post('dropzone', function(\Illuminate\Http\Request $request) {
    Log::info(var_export($request->all(), true));
    return "OK";
});

View

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dropzone Test</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.css" rel="stylesheet">
    <style>
        .dropzone {
            border: 2px dashed #0087F7;
            border-radius: 5px;
            background: white;
            padding: 10px 10px;
            margin: 1em 0;
        }
        .dropzone .dz-message {
            color: rgb(100, 108, 127);
            font-weight: 400;
            font-size: 20px;
            text-align: center;
            margin: 2em 0;
        }
    </style
</head>
<body>
    <form action="{{ action('supportController@postTicket') }}" method="post">
        {{ csrf_field() }}
        <input type="text" name="testinput" value="Hi there">
        <button type="submit">Submit</button>
    </form>
    <form action="{{ asset('dropzone') }}" method="post" class="dropzone" id="uploads">
        {{ csrf_field() }}
    </form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.js"></script>
</body>
</html>

When you drop a file onto the upload form you will see something like this in your log:

'_token' => '2NRIRO6cIlS7FoiYSG4tegk9v9z1fv2qMlCB6cmf',
  'file' => 
  Symfony\Component\HttpFoundation\File\UploadedFile::__set_state(array(
     'test' => false,
     'originalName' => 'test.png',
     'mimeType' => 'image/png',
     'size' => 14257,
     'error' => 0,
  )),

So the uploaded file will be available as file and you can get it like this:

if ($request->hasFile('file')) {
    $uploadedFile = $request->file('file');
}
t0berius's avatar

@skliche

What a mess ;D. The code itself works, but when I try to check if a file was submitted it says all the time no, even if I submit one using dropzone. I've deactivated CRF protection for the POST route and for the dropzone route. controller:

 if ($request->hasFile('file'))
        dd("ya");
    else
        dd("no");

Is there a way to get the submit button below the "dropzone" field? I know why I hate JS, this is way, so many problems all the time.

skliche's avatar

@jaheller Those are two completely different forms. When you submit your "regular" form, the file will not be transmitted. The file will be transmitted as soon as you drop a file onto the area. If you don't feel comfortable with this, stick to the other method I mentioned (MAX_FILE_SIZE) and keep everything within the same form.

t0berius's avatar

@skliche Because I think users requests need to be validated and I don't want to show them just a blank php error screen I would need to have some kind of clientside verification before a file is submitted, but it seems like dropzone.js isn't comfortable for doing this task, I need to get the entry from all forms...

skliche's avatar

@jaheller Dropzone allows you to set a maximum file size and the allowed mime types so that it prevents you from uploading unwanted files. E.g.

<script>
Dropzone.options.uploads = {
    maxFilesize: 1,
    acceptedFiles: "text/plain"
};
</script>

I don't think plain HTML allows you to perform that kind of client side restrictions for file uploads without using Javascript.

t0berius's avatar

My question is now how can I get the file (in case of exists) and the sent value from the forms in one controller? I need to insert all the values into one database, that's why I need to get them in one method, do you understand my need?

skliche's avatar

@jaheller Ok, in that case it's better to use a single form:

<body>
    <form action="{{ asset('dropzone') }}" method="post" class="dropzone" id="uploads">
        {{ csrf_field() }}
        <div class="dropzone-previews"></div>
        <input type="text" name="testinput" id="testinput" value="Hi there">
        <button type="submit" id="submit-form">Submit</button>
    </form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.js"></script>
<script>
    Dropzone.options.uploads = {
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 100,
        maxFiles: 100,
        maxFilesize: 1,
        acceptedFiles: ".jpg, .png",
        init: function() {
            var myDropzone = this;
            this.element.querySelector("#submit-form").addEventListener("click", function(e) {
                if (myDropzone.getQueuedFiles().length>0) {
                    e.preventDefault();
                    e.stopPropagation();
                    myDropzone.processQueue();
                }
            });
        }
    };
</script>

That deactivates the automatic upload and uses the submit button to trigger an upload of both the form fields as well as the queued files that have been dropped onto the form.

t0berius's avatar

1.) If there's a way hot to get the "input" form out of the dropzone field and the button below the dropzone field the design would be fine.

screen atm: http://puu.sh/mzhO4/01f104eed9.png

2.) Is there a way to "lock" the submit button, in case of file is "not accepted", meaning dropzone detectes file is too big or file extension is not allowed etc.?

My whole view atm:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dropzone Test</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.css" rel="stylesheet">
    <style>
        .dropzone {
            border: 2px dashed #0087F7;
            border-radius: 5px;
            background: white;
            padding: 10px 10px;
            margin: 1em 0;
        }
        .dropzone .dz-message {
            color: rgb(100, 108, 127);
            font-weight: 400;
            font-size: 20px;
            text-align: center;
            margin: 2em 0;
        }
    </style>
</head>
<body>
    <form action="{{ asset('dropzone') }}" method="post" class="dropzone" id="uploads">
        {{ csrf_field() }}
        <div class="dropzone-previews"></div>
        <input type="text" name="testinput" id="testinput" value="Hi there">
        <button type="submit" id="submit-form">Submit</button>
    </form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.js"></script>
<script>
    Dropzone.options.uploads = {
        autoProcessQueue: false,
        uploadMultiple: false,
        parallelUploads: 100,
        maxFiles: 100,
        maxFilesize: 1,
        acceptedFiles: ".jpg, .png .exe",
        init: function() {
            var myDropzone = this;
            this.element.querySelector("#submit-form").addEventListener("click", function(e) {
                if (myDropzone.getQueuedFiles().length>0) {
                    e.preventDefault();
                    e.stopPropagation();
                    myDropzone.processQueue();
                }
            });
        }
    };
</script>

</body>
</html>
skliche's avatar
skliche
Best Answer
Level 42

@jaheller Positioning is just a matter of CSS. The following re-arranges the elements and prevents submission if rejected files are present.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Dropzone Test</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.css" rel="stylesheet">
    <style>
        .dropzone {
            border: none;
        }
        .dropzone-container {
            border: 2px dashed #0087F7;
            border-radius: 5px;
        }
        .dropzone-previews {
            background: white;
            padding: 10px 10px;
            margin: 1em 0;
        }
        .dropzone .dz-message {
            color: rgb(100, 108, 127);
            font-weight: 400;
            font-size: 20px;
            text-align: center;
            margin: 1em 0;
        }
    </style>
</head>
<body>
    <form action="{{ asset('dropzone') }}" method="post" class="dropzone" id="uploads">
        {{ csrf_field() }}
        <input type="text" name="testinput" id="testinput" value="Hi there">
        <div class="dropzone-previews"></div>
        <div class="dropzone-container">
            <div class="dz-message">Drop files here</div>
        </div>
        <button type="submit" id="submit-form" style="margin-top: 2em">Submit</button>
    </form>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.js"></script>
<script>
    Dropzone.options.uploads = {
        autoProcessQueue: false,
        uploadMultiple: true,
        parallelUploads: 100,
        maxFiles: 100,
        addRemoveLinks: true,
        previewsContainer: ".dropzone-previews",
        clickable: ".dz-message",
        maxFilesize: 1,
        acceptedFiles: "text/plain",
        init: function() {
            var myDropzone = this;
            this.element.querySelector("#submit-form").addEventListener("click", function(e) {
                if (myDropzone.getRejectedFiles().length>0) {
                    e.preventDefault();
                    e.stopPropagation();
                    return;
                }
                if (myDropzone.getQueuedFiles().length>0) {
                    e.preventDefault();
                    e.stopPropagation();
                    myDropzone.processQueue();
                }
            });
        }
    };
</script>
</body>
</html>

Please or to participate in this conversation.