I have a website up and running which makes use of file uploads. Everything is working fine, except for one of the users. They are using IE8 to upload files from their SharePoint server to the website. When I look at the $_FILES variable in PHP the 'name' key looks like this:
somefilename[1]
Instead of
somefilename.pdf
The uploads are then blocked, because the extension is not allowed. Has anyone ever dealt/seen this before? It looks like a temporary name, or a hidden file extension.
Edit:
Some of you requested the $_FILES variable:
[Filedata] => Array
(
[name] => Algemene%20Voorwaarden%20Corporate%20Services%202011[2]
[type] => application/octet-stream
[tmp_name] => /tmp/phps19zye
[error] => 0
[size] => 148021
)
This should be a PDF file. I need the extension, not only for security reasons, the [type] would be better suited for that, but also for presentation and functionality. I need to display the correct icon for a file type, and separate images for processing.
The HTML form is just a basic test form:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="uploadtest3.php" method="post" enctype="multipart/form-data">
<input type="file" name="file_upload" id="file_upload" />
<br /><input type="submit" value="Uploaden" />
</form>
</body>
</html>
The PHP file is the following:
$targetFolder = '/uploadtests/uploads3';
if (!empty($_FILES)) {
$tempFile = $_FILES['file_upload']['tmp_name'];
$targetPath = $_SERVER['DOCUMENT_ROOT'] . $targetFolder;
$targetFile = $targetPath . $_FILES['file_upload']['name'];
move_uploaded_file($tempFile,$targetFile);
echo "OK";
}
Introduction
Have seen this issue before but am not sure what caused it. I would not even like to call it an error because some files extension can be intentionally removed or altered for malicious purpose.
The most important thing is validating file properly and worry less if a file has extension or not
Reasons :
File Extension Can easily be faked and it would be bad if your application relies on file extension only for validation
$_FILES ['file_upload']['type'] would return application/octet-stream for all files with not extension so it not not also a option for validation
Since its a browser issue then its a Client Related Problem so you don't have any control. If you are able to manage this you would definitely increase user experience
Simple Patch
The solution is very simple. All you need to validate your file with FILEINFO and fix any extension issue to your uploaded file.
You also need to validated all uploaded file based on their Mime Type ... and remove any invalid file.
Prove of Concept
$allowedTypes = array (
"pdf" => "application/pdf"
);
$pathFinal = "final";
$pathTemp = "temp";
try {
if (! empty ( $_FILES )) {
$tempFile = $_FILES ['file_upload'] ['tmp_name'];
$fileName = $_FILES ['file_upload'] ['name'];
$destinationTemp = $pathTemp . DIRECTORY_SEPARATOR . $fileName;
$destinationFinal = $pathFinal . DIRECTORY_SEPARATOR . $fileName;
/**
* Move To tempary File
*/
move_uploaded_file ( $tempFile, $destinationTemp );
$fileMime = mimeInfo ( $destinationTemp );
$key = array_search ( $fileMime, $allowedTypes );
/**
* Validate Mime Type
*/
if (empty ( $key )) {
unlink ( $destinationTemp );
throw new Exception ( "File Type not Supported" );
}
/**
* Fix Extention Issues
*/
$ext = pathinfo ( $destinationTemp, PATHINFO_EXTENSION );
if (empty ( $ext )) {
$destinationFinal .= "." . $key;
}
/**
* Transfer File to Original Location
*/
copy ( $destinationTemp, $destinationFinal );
unlink ( $destinationTemp );
echo "OK";
}
} catch ( Exception $e ) {
echo "ERROR :", $e->getMessage ();
}
Function Used
function mimeInfo($file) {
return finfo_file ( finfo_open ( FILEINFO_MIME_TYPE ), $file );
}
somefilename appears to be an array. Are your users selecting more than one file to upload?? You need to limit the number of files allowed to upload, or catch the array and handle it properly. You might also ensure that Sharepoint is set to allow multiple file uploads.
Also, I found information regarding the way that IE8 passes the file upload to the server. The source article states:
Additionally, the “Include local directory path when uploading files” URLAction has been set to "Disable" for the Internet Zone. This change prevents leakage of potentially sensitive local file-system information to the Internet. For instance, rather than submitting the full path C:\users\ericlaw\documents\secret\image.png, Internet Explorer 8 will now submit only the filename image.png.
This means, if your code (or Sharepoint) is looking to delimit the folder structure from filename by PATH_SEP, then with IE8 it will obviously fail.
You should check the file type not by extension but by Mime type. Use the variable $_FILES["uploaded_file"]["type"] to get the Mime type.
This way users cannot tamper with the extension with weird ASCII characters, and you know what file is what reliably.
Related
I'm trying to build a basic upload form to add multiple files to a folder, which is processed by PHP.
The HTML code I have is:
<form id="form" action="add-files-php.php" method="POST" enctype="multipart/form-data">
<div class="addSection">Files To Add:<br><input type="file" name="files[]" multiple /></div>
<div class="addSection"><input type="submit" name="submit" value="Add Files" /></div>
</form>
And the PHP to process is:
$file_path = "../a/files/article-files/$year/$month/";
foreach ($_FILES['files']['files'] as $file) {
move_uploaded_file($_FILES["file"]["name"],"$file_path");
}
I can run the PHP without any errors, but the files don't get added to the path folder.
Where am I going wrong with this?
I have a similar code actually in one of my projects. Try it.
foreach ($_FILES['files']['name'] as $f => $name) {
move_uploaded_file($_FILES["files"]["tmp_name"][$f], $file_path);
}
Look at the following page:
http://php.net/manual/en/function.move-uploaded-file.php
EDIT:
Nowhere in the code you provided, does it show that you actually give your file a filename, you simply refer to a path, rather than a path+filename+extension
move_uploaded_file($_FILES["files"]["tmp_name"][$f], $file_path . $name);
modifying my original code sample to be like the second one, should work.
Iterate the $_FILES['files']['error'] array and check if the files are actually uploaded to the server:
$dest_dir = "../a/files/article-files/$year/$month";
foreach ($_FILES["files"]["error"] as $key => $error) {
if ($error == UPLOAD_ERR_OK) {
// The temporary filename of the file stored on the server
$tmp_name = $_FILES["files"]["tmp_name"][$key];
$name = basename($_FILES["files"]["name"][$key]);
// Handle possible failure of the move_uploaded_file() function, too!
if (! move_uploaded_file($tmp_name, "$dest_dir/$name")) {
trigger_error("Failed to move $tmp_name to $dest_dir/$name",
E_USER_WARNING);
}
} else {
// Handle upload error
trigger_error("Upload failed, key: $key, error: $error",
E_USER_WARNING);
}
}
The biggest issue with your code is that you are trying to move $_FILES['files']['name'] instead of $_FILES['files']['tmp_name']. The latter is a file name of temporary file uploaded into the temporary directory used for storing files when doing file upload.
P.S.
Using relative paths is error-prone. Consider using absolute paths with the help of a constant containing path to the project root, e.g.:
config.php
<?php
define('MY_PROJECT_ROOT', __DIR__);
upload.php
<?php
require_once '../some/path/to/project/root/config.php';
$dest_dir = MY_PROJECT_ROOT . "/a/files/article-files/$year/$month";
With PHP 5 I would like to upload an XML file via a web form and parse it using SimpleXML.
I've tried few SimpleXML examples and they work fine at my CentOS 6 Linux server.
However, I don't have any experience with handling uploaded files in PHP yet.
Should I use the $_FILES and do I always have to use a temporary file or can it be done completely in memory?
From PHP Cookbook I've copied this example:
<html>
<body>
<?php if ($_SERVER['REQUEST_METHOD'] == 'GET') { ?>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME'] ?>"
enctype="multipart/form-data">
<input type="file" name="doc"/>
<input type="submit" value="Send File"/>
</form>
<?php } else {
if (isset($_FILES['doc']) &&
($_FILES['doc']['error'] == UPLOAD_ERR_OK)) {
$oldPath = $_FILES['doc']['tmp_name'];
$newPath = '/tmp/' . basename($_FILES['doc']['name']);
if (move_uploaded_file($oldPath, $newPath)) {
print "File moved from $oldPath to $newPath";
} else {
print "Couldn't move $oldPath to $newPath";
}
} else {
print "No valid file uploaded.";
}
}
?>
</body>
</html>
It works fine and for the print_r statement added by me the following output is printed:
Array
(
[document] => Array
(
[name] => my_file.xml
[type] => text/xml
[tmp_name] => /tmp/phpRD9cYI
[error] => 0
[size] => 1610252
)
)
And I can see the /tmp/my_file.xml file.
My question is though if I can skip the creation of temporary files?
I don't like them because:
They are sometimes security issue
They have to be cleaned up (by a cronjob?)
Their names might collide (probably seldom case unless it's 1)
UPDATE: Also, I don't understand, why can't I read the file at $oldPath? It is not found there and I have to call the move_uploaded_file() and then read the $newPath...
You don't need to save/move the file in a new folder,
if (isset($_FILES['doc']) && ($_FILES['doc']['error'] == UPLOAD_ERR_OK)) {
$xml = simplexml_load_file($_FILES['doc']['tmp_name']);
}
The tempfile generated will be automatically removed when the PHP script finishes. Hope this helps.
I am able to get the web path to the file like so:
$filename = 'elephant.jpg';
$path_to_file = $this->getSkinUrl('manufacturertab');
$full_path = $path_to_file . '/' . $filename;
But if the file doesn't exist, then I end up with a broken image link.
I tried this:
if(!file_exists($full_path)) {
Mage::log('File doesn\'t exist.');
} else {
?><img src="<?php echo $full_path ?>" /><?php
}
Of course that didn't work because file_exists does not work on urls.
How do I solve this?
1.)
Can I translate between system paths and web urls in Magento?
e.g. something like (pseudocode):
$system_path = $this->getSystemPath('manufacturertab');
That looks symmetrical and portable.
or
2.)
Is there some PHP or Magento function for checking remote resource existence? But that seems a waste, since the resource is really local. It would be stupid for PHP to use an http method to check a local file, wouldn't it be?
Solution I am currently using:
$system_path = Mage::getBaseDir('skin') . '/frontend/default/mytheme/manufacturertab'; // portable, but not pretty
$file_path = $system_path . '/' . $filename;
I then check if file_exists and if it does, I display the img. But I don't like the asymmetry between having to hard-code part of the path for the system path, and using a method for the url path. It would be nice to have a method for both.
Function
$localPath = Mage::getSingleton( 'core/design_package' )->getFilename( 'manufacturertab/' . $filename, array( '_type' => 'skin', '_default' => false ) );
will return the same path as
$urlPath = $this->getSkinUrl( 'manufacturertab/' . $filename );
but on your local file system. You can omit the '_default' => false parameter and it will stil work (I left it there just because getSkinUrl also sets it internaly).
Note that the parameter for getSkinUrl and getFilename can be either a file or a directory but you should always use the entire path (with file name) so that the fallback mechanism will work correctly.
Consider the situation
skin/default/default/manufacturertab/a.jpg
skin/yourtheme/default/manufacturertab/b.jpg
In this case the call to getSkinUrl or getFilename would return the path to a.jpg and b.jpg in both cases if file name is provided as a parameter but for your case where you only set the folder name it would return skin/yourtheme/default/manufacturertab/ for both cases and when you would attach the file name and check for a.jpg the check would fail. That's why you shold always provide the entire path as the parameter.
You will still have to use your own function to check if the file exists as getFilename function returns default path if file doesn't exist (returns skin/default/default/manufacturertab/foo.jpg if manufacturertab/foo.jpg doesn't exist).
it help me:
$url = getimagesize($imagepath); //print_r($url); returns an array
if (!is_array($url))
{
//if file does not exists
$imagepath=Mage::getDesign()->getSkinUrl('default path to image');
}
$fileUrl = $this->getSkinUrl('images/elephant.jpg');
$filePath = str_replace( Mage::getBaseUrl(), Mage::getBaseDir() . '/', $fileUrl);
if (file_exists($filePath)) {
// display image ($fileUrl)
}
you can use
$thumb_image = file_get_contents($full_path) //if full path is url
//then check for empty
if (#$http_response_header == NULL) {
// run check
}
you can also use curl or try this link http://junal.wordpress.com/2008/07/22/checking-if-an-image-url-exist/
Mage::getBaseDir() is what you're asking for. For your scenario, getSkinBaseDir() will perform a better job.
$filename = 'elephant.jpg';
$full_path = Mage::getDesign()->getSkinBaseDir().'/manufacturertab/'.$filename;
$full_URL=$this->getSkinUrl('manufacturertab/').$filename;
if(!is_file($full_path)) {
Mage::log('File doesn\'t exist.');
} else {
?><img src="<?php echo $full_URL ?>" /><?php
}
Note that for the <img src> you'll need the URL, not the system path. ...
is_file(), rather than file_exists(), in this case, might be a good option if you're sure you're checking a file, not a dir.
You could use the following:
$file = 'http://mysite.co.za/files/image.jpg';
$file_exists = (#fopen($file, "r")) ? true : false;
Worked for me when trying to check if an image exists on the URL
As a bit of a follow up to Javascript form won't submit (to view the code I am using visit that link) I am now encountering a problem that I cannot find the file that has been uploaded.
I have added $files = apc_fetch('files_'.$_POST['APC_UPLOAD_PROGRESS']); to the top of my page and this is the output of print_r($files);
Array
(
[theFile] => Array
(
[name] => tt1.mp4
[type] => video/mp4
[tmp_name] => /tmp/php2BEvy7
[error] => 0
[size] => 1050290
)
)
However when I try to run the following code:
if (file_exists($files['theFile']['tmp_name'])) {
$webinarType = strcmp($files['theFile']['type'], 'video/mp4');
if($webinarType == 0) {
$webinarFile = $fileTitle;
$webinarTempName = $files['theFile']['tmp_name'];
} else {
echo 'Webinar must be .mp4';
}
} else {
echo "No File";
}
I get the No File output.
I have ssh'd into the server and the file is not in /tmp/, /path/to/public_html/tmp/ or path/to/file/tmp/ all of which exist.
I have tried to use move_uploaded_file() but as this is executed on all file inputs I can't get the tmp_name dynamically due to my limited knowledge of javascript.
tl;dr version; Where is my file gone and how can I find it?
NOTE; This form did work before the APC intevention and I am running wordpress in case that affects anything.
Fixed this one on my own as well.
In the progress.php file (found on the other question) I modified the elseif statement with this:
elseif(($s_progressId = $_POST['APC_UPLOAD_PROGRESS']) || ($s_progressId = $_GET['APC_UPLOAD_PROGRESS']))
{
// If the file has finished uploading add content to APC cache
$realpath = realpath($PHP_SELF);
$uploaddir = $realpath . '/tmp/';
foreach ($_FILES as $file) {
if(!empty($file['name'])) {
$uploaded_file = $file['name'];
$moveme = $uploaddir.$uploaded_file;
move_uploaded_file($file['tmp_name'], $moveme);
}
}
apc_store('files_'.$s_progressId, $_FILES);
die();
}
That way I could iterate through the $_FILES array without knowing the name of the input. I noticed that it loops through a couple of times hence the if(!empty()) however in hindsight it's probably best practice anyway.
I have a process whereby I take a picture on a phone and upload it to a PHP script on my server.
It usually works, but *sometimes the phone takes a picture that has SOMETHING about it that the PHP doesn't like. I have copied the image to my computer and attempted to upload it through a standard browser upload form but get the same error.
My question is, how do I find out what the PHP is not liking about the image? (it appears to be a perfectly legitimate JPG, just like all the others that DO work).
My PHP looks like this:
if ( move_uploaded_file( $_FILES[ 'file' ][ 'tmp_name' ], $folder . $fileName1 ) ) {
echo "SUCCESS";
}
else{
"FAIL:";
foreach (getallheaders() as $name => $value) {
echo "|$name: $value| ";
}
}
I'd like to print and save some error info about what is going wrong here, but am not sure how to introduce what, in effect, would be a catch block in PHP.
All help appreciated.
The $_FILES array contains a field that describes any error that occurred: $_FILES['file']['error']. You can check them out here http://ca.php.net/manual/en/features.file-upload.errors.php
Could it be that the files you are trying to upload are too big?
<?php
if($_FILES['file']['error']) {
$Errors .= $_FILES['file']['error'];
} else {
// process
}
?>