Limit File Uploads To One File - PHP - php

I have a file input element that is used to upload a profile image to a user profile page. By default browsers only allow this element to upload one file unless you add the multiple attribute.
I'd like to set a back up in the PHP though just in case someone decides to add the 'multiple' attribute in the HTML.
I thought assigning the $_POST superglobal to a variable and having an if statement saying if this value is greater than 1 would be prevent this, but it doesn't?
What is the best way to approach this? I've tried various things such as the count() array method but can't seem to find a solution to what seems like a very simple problem?
if(isset($_POST['submit-profile-image'])) {
$profileImage = $_POST['submit-profile-image'];
if (isset($profileImage)) {
if ($profileImage > 1) {
$error[] = "You cannot upload more than one profile image";
}
}
// ALL OTHER CODE
}
I've also tried using the $_FILES superglobal and counting the instances of the $_FILES['profile-image']. This encounters a different problem in that it blocks more than one file upload BUT also blocks single file uploads (and I don't understand why)?
if(isset($_FILES['profile-image'])){
if(count($_FILES['profile-image']) > 1){
$error[] = "You cannot upload more than one profile image";
}
}

You can't prevent a user sending multiple files. All you can do is defend against the possibility, and fail gracefully.
Let's assume that your HTML includes this <form>
<form method='post' enctype="multipart/form_data">
<input type='file' name='uploadFile'>
<input type='submit' name='submit'>
</form>
When the user selects a file and clicks submit the browser packs up the file and sends it, PHP unpacks the file to the server disk, and then presents the file details to your program in the $_FILES['uploadFile'] array.
If we assume that the user edits your HTML and adds multiple then the browser will pack up the files and send them. PHP will unpack the first file and add its detail to $_FILES['uploadFile'] as before. It will then unpack the second file and place its details in $_FILES['uploadFile'], overwriting the first file. Your program sees only one file, knows nothing of any other file, and carries on.
To get two files your user will have to change the name of the file input to use array syntax, so lets suppose he changes the line to
<input type='file' name='uploadFile[]' multiple>
Now PHP unpacks the file details into a set of arrays. Instead of having $_FILES['uploadFile']['name'] containing a string with one filename, it becomes an array of strings.
It is likely that your code, expecting a string, will choke on an array and fail in some unexpected way. You can check for this condition with
if (is_array($_FILES['uploadFile']['name'])) {throw new Exception("Too many files");}
So, our user, determined to force this extra file on you now adds a second <input> to the form:
<form method='post' enctype="multipart/form_data">
<input type='file' name='uploadFile'>
<input type='file' name='uploadFile'>
<input type='submit' name='submit'>
</form>
The second file overwrites the first as it has the same name. Your program is none the wiser and carries on with just one file. So the user changes the name on the second input:
<form method='post' enctype="multipart/form_data">
<input type='file' name='uploadFile'>
<input type='file' name='uploadFileB'>
<input type='submit' name='submit'>
</form>
You could check for this by looking at count($_FILES), but your program isn't looking for a second input, so it will ignore it anyway and carry on handling just the first file. If the user also changes the first name your program won't see any files, and if he reverses the names, your program will see just the second file and ignore the first.
Alternatively, set the PHP configuration value in PHP.INI:
max_file_uploads = 1;
If you do this, PHP will ignore the second and subsequent files. Your code will still have to deal with the naming issues. Setting this with ini_set() doesn't seem to have any effect.

Related

Fill file input after form submit / on form submit error

I´ve a multipart form with a mixture of default inputs (text, select etc.) and a file upload (<input type="file">).
I´m using a combination of form validation class and upload library of Codeigniter for form submission. That works great.
I´ve only one problem for what I haven´t found a solution yet: If the user selects an image but misses to fill another required field (like "name"), then the form validation class blocks the request and shows an error message to the customer.
But now I´ve the problem, that the image was already submitted successfully and I don´t want to let the user add the file again. So I want to pre-fill the file input with this data.
I´ve tried different things like:
<input type="file" name="image" value="<?php echo set_value('image','');?>" />
and also spent time on finding a solution on the web but without success.
On the server side, you do not get any information about where the file is located on the client's computer, so in the scenario of a user uploading an image successfully but the user hasn't filled out the rest of the fields properly, you have to simply omit the input type="file" field entirely but keep a store of where the file is located on your server. There's a few ways to go about this, but it all involves taking the absolute location of the uploaded file and:
Inserting it back as a hidden value using <input type="hidden" name="uploadedFile" value="<?php echo $absPath; ?>" /> then checking for the existence of $_POST['uploadedFile'] and utilizing it appropriately. But this isn't a solid idea as you're now exposing server paths to the end-user (opens yourself up to malicious attack.)
Starting a session and saving the absolute path in the $_SESSION variable while presenting the user with a simple token in their re-attempt form.
I'd stick with method 2, so assuming you've done all the work to validate the form and upload the file and your file is located in $absFilePath, you could do the following:
session_start(); // This needs to be at the very top of you PHP file
// ...
$formToken = md5(time());
$_SESSION['uploadedFile'][$formToken] = $absFilePath;
Then render the token as a hidden variable using:
if (!empty($_SESSION['uploadedFile'][$formToken]))
echo '<input type="hidden" name="formToken" value="'.$formToken.'" />';
and hide the file upload portion using
if (empty($_SESSION['uploadedFile'][$formToken]))
echo // <input type="file" output here...
finally inside of your form submission code check for the existence of a formToken value before attempting to load $_FILES['image'] using isset($_POST['formToken']), and handle it using:
$absFilePath = $_SESSION['uploadedFile'][$_POST['formToken']];
Bam! Now you have your absolute file path as if the file had been uploaded just like before.
Since you haven't given enough code, I can only given you enough instruction to get you started, but this should be more than enough.

Pass uploaded file between PHP scripts

I've got a page where users can upload a HTML file for use as a theme. The HTML file has some checks performed on it before some options are displayed to the user. The user fills out the form in relation to the HTML file, and submits the form. However, due to the file in the temp folder being destroyed after the script has ended, I do not know how to get the HTML file once the form has been filled out and re-submitted short of making the user re-upload the file, which relies on them uploading the same file, and also makes them upload something twice, which is counter-intuitive and could be an issue with large files.
Here is the code (cut down to make it easier to read/understand). The form submits to itself, so the same PHP file is used for both "steps".
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Form has been submitted
$files = $_FILES['file'];
<form method="POST">
/* Options about the uploaded HTML file are generated using PHP and displayed here */
<input type="submit">
</form>
} else {
?>
<form method="POST" enctype="multipart/form-data">
<label for="file">Theme File(s)</label>
<input type="file" name="file[]" multiple="multiple">
<input type="submit">
</form>
<?php
}
I tried using <input type="file" name="file[]" multiple="multiple" value="<?php echo $files; ?> >, but this did not work, and would also require the user to re-upload the files, which could be any issue if the files get too big.
I was thinking there might be a way to pass the file internally, and have a check whether a file has been uploaded or passed to the script instead of <input type="file" name="file[]" multiple="multiple">, but I could not find a way to do that.
You need to create your own temporary file, and pass the name between your two scripts.
So for example, in your "first script" (i.e. when the file has first been uploaded) you would:
$uniqName = uniqid('upload_', TRUE);
$tmpFile = "/tmp/$uniqName.html";
move_uploaded_file($_FILES['file']['tmp_name'][0], $tmpFile);
And then when you generate the form from the result of this upload, you would add
<input type="hidden" name="uniqName" value="<?php echo $uniqName; ?>" />
...so that when you get to your "second script" (after the questionnaire form is submitted) you can access the file through:
$tmpFile = "/tmp/".basename($_REQUEST['uniqName']).".html";
Of course, this is subject to the possibility of people failing to submit to second form so you end up with "orphaned files" littering your temporary directory, so you will need to implement some form of check that deletes files after thet have been inactive for a certain amount of time - you can base this on the last modified time of the files.
EDIT
Here is an example of how you can randomly run a job to keep the /tmp dir tidy without a cron job:
$probabilityDivisor = 10; // 1/10 chance of running
$uploadTTLSecs = 1800; // 30 minutes
if (mt_rand(1, $probabilityDivisor) / $probabilityDivisor == 1) {
foreach (glob('/tmp/upload_*') as $file) {
// Iterate files matching the uniqids you generate
if (time() - filemtime($file) >= $uploadTTLSecs) {
// If file is older than $uploadTTLSecs, delete it
unlink($file);
}
}
}
This operates in a similar way to the PHP session garbage collectors. Because of the simplicity of the operations, this should not adversely affect the user experience in any meaningful way.
You could try the following:
Use move_uploaded_file() and move the file from the temp location to a permanent location on the server. Be sure to give the file your moving a unique name like 'file-' . time() . '.jpg' or something.
Soon after uploading, register a Session variable and put the file name in it.
After everything is over you could delete the file using unset()
Now the file is located safely on your server and you also have access to it via the session.
Hope this helps :)

HTML form for PHP file upload: no textbox

My PHP book gives a template HTML form for uploading a file:
<form action="upload.php" method="post" enctype="multipart/form-data"/>
<div>
<input type="hidden" name="MAX_FILE_SIZE" value="10000000"/>
<label for="userfile">Upload a file:</label>
<input type="file" name="userfile" id="userfile"/>
<input type="submit" value="Send File"/>
</div>
</form>
The book displays it as "Upload a file:" [textbox] [Browse...] [Send File]
I copied it verbatim, and the result I'm getting is "Upload a file:" [Choose File] "no file chosen" [Send File]
I'm wondering why the discrepancy exists. Is there a way around it? I'm using XHTML Transitional. No doctype is given in the book. But I doubt that's the issue.
The script I'm writing aims to take the file the user chooses, process it, and write the result into another file that doesn't exist yet. I'm asking this question because it would be useful to let the user more easily copy the initial file path/name, paste it into the other field, and just change a part of it.
(Also: why the difference between "Browse..." and "Choose File"? I tried manually setting the value of the "userfile" field to "Browse..." but nothing happened. This is less important but I'm curious nonetheless.)
It is probably showing a different browser and/or version.
It sounds like you are looking at it under Safari and the book has screenshots of IE, for example.
There are a few ways to get complete control of file uploading and the <input type="file" /> element. You can use Flash, or you can set the input to opacity: 0 and then position what you want beneath it.
Some time ago the browser engines took almost complete control over the input type="file" - fields, since it nowadays is regarded as a security issue. For example the days before that you could easily prefill the file input filed with some path and filename (e.g. something like /etc/passwd) and hide the field, so sending the form you would not remark that you're also sending the file...
That's why for example you could not preset the filename of such a field and that's also why browsers now all do their own thing with these special input fields.
As Alex said above, you could get around this, but it will be some hassle, because it would mean to "fake" the file input field.

Refering to arrays of form elements (name="blah[]")

I have a form with series of 3 file upload fields, each of which has a related hidden "todo" fields.
The file upload fields start greyed out and a user can either upload a new file, remove a file if one has previously been uploaded in that position or leave it unchanged (i.e. use the previously uploaded file or leave it blank).
The todo fields should store what is to be done with each file (i.e. 0=upload new, 1=delete existing, 2=leave unchanged).
I have a series of buttons next to the upload field. One for "upload new" (which enables the file upload field and (should) set the related todo field to 0; one for remove (which disables the file upload box); and one for "leave unchanged" (which also disables the file upload field).
I've found the name="blah[]" technique for creating arrays when the form is posted to a PHP document which makes looping through the files nice and easy. The trouble is that I need to edit the value in the related "todo" fields and if they're all named "todo[]" then I can't refer to one specifically...
The code is something like this:
<input type="file" name="file[]" />
<input type="hidden" name="todo[]" />
<input type="button" onclick="enableFileField('file[]', 0)" value="Upload New" />
<input type="button" onclick="enableFileField('file[]', 1)" value="Remove Current" />
<input type="button" onclick="enableFileField('file[]', 2)" value="No Change" />
I'm pretty sure I'm missing something and that this is actually quite simple...
You can give the fields ids in addition to names. The name would be used for the post to the server, but the id can be used for referencing the input in JavaScript:
<input type='hidden' id='todo_0' name='todo[]'>
<input type='hidden' id='todo_1' name='todo[]'>
In JavaScript, document.getElementById("todo_0") will give you the first todo field. Be sure to keep the ids sufficiently different that Internet Explorer doesn't get confused (it has namespace bugs around id and name [it tends -- completely incorrectly -- to put them in the same namespace]).
You could increment a counter in javascript as you add more fields, so you create todo[0], todo[1], etc. This wouldn't change how PHP interprets it.
Edit:
Realised you aren't creating fields on the fly in javascript, but the naming still applies
You could give each of the todo inputs a unique ID that you remember, or, I believe you can use
<input type='hidden' name='todo[0]' />
<input type='hidden' name='todo[1]' />
etc. in your HTML.
If I understand what you are asking, you want to be able have to multiple fields that will be used to upload a file. For example, if you have 3 files to modify, you would have three hidden todo fields?
A quick and easy solution would be to keep a hidden field for the number of files such as:
<input type='hidden' name='numFiles' value='1' />
and update that as you add or remove files with javascript. Then as others have suggested, give each todo a unique id as such:
<input type='hidden' name='todo1' />
Now you can easily find a todo because each file will have a unique one and you will be able to update it from there.
Once you post the form, you can pull the number of files there will be from the numFiles field and loop through all the todo's with a number appended to the end.

Upload files using list box in PHP

I want to upload files from a list box in php.
I am able to do it by using <input type="file"> which I found on http://www.tizag.com/phpT/fileupload.php
But when I change this <input type="file"> by <select>
i am trying this way
<form enctype="multipart/form-data" action="upload.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" />
<select name="uploadedfile" id = "fileName" size="3" style="width: 100%">
<option id = "uploadedfile" value="c:\text.txt">c:\text.txt</option>
</select>
<input type="submit" value="Upload File" />
</form>
and PHP code remains the same for both cases
<?php
$target_path = "uploads/";
$target_path = $target_path . basename( $_FILES['uploadedfile']['value']);
if(move_uploaded_file($_FILES['uploadedfile']['tmp_name'], $target_path)) {
echo "The file ". basename( $_FILES['uploadedfile']['value']).
" has been uploaded";
} else{
echo "There was an error uploading the file, please try again!";
}
?>
it does not work........
Regards
Hemant
You can only use an <input type="file" /> to upload files for security reasons. These input types display a file select box and allow a user to select a file in a secure way. Allowing a server to select arbitrary files to upload like you are trying to do would be a gross breach of security.
For instance, say I implemented your <select> based option (and it worked). I could select your Windows password file to upload. I could select all sorts of nasty files that are in predefined locations.
As a total aside, your HTML has two elements with the same name. Which one is actually sent to the server will be somewhat dependant on your browser and server. You really only want one form element with the same name.
I'm not sure how this is supposed to work, since you are using a drop down box to ask a user to upload a file.
Drop down menus (select > option) are not, in my experience, used as inputs other than for specific choices, like "blue" vs "red".
however, you are going to run into issues with your setup because a) you the first file option outside of the select element and b) you gave both of them the same name, which means that when php gets the POST variable, it's going to create an array with two items with the same key (if it is even getting to that point).
Update
After reading Matthew's answer, I now notice the c:/text.txt you have set as the value. As he said, that's a big time no. You could in theory have it copy the entire hard drive (very slowly of course) or have some AJAX that doesn't even ask the user if they are okay with the upload and get anything on the computer.
I thought somehow you were offering the user the option to "upload" some generic file already on the server because they had nothing they could use on their end.

Categories