I found this snippet that says will only allow certain file types. Will it work and could someone bypass it to upload what ever file type they want? And could someone explain the substr part, i don't get how it works..
<?php
function CheckExt($filename, $ext) {
$name = strtolower($filename);
if(substr($name, strlen($name) -3, 3) == $ext)
return true;
else
return false;
}
?>
A better way to check extensions
function checkExt($filename, $ext)
{
$fnExt = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
if(!is_array($ext)) {
$ext = (array)$ext;
}
$ext = array_map('strtolower', $ext);
return in_array($fnExt, $ext);
}
You can then call it like
var_dump(checkExt('test.temp', 'tmp')); // false
var_dump(checkExt('test.temp', array('tmp', 'temp'))); // true
Avoid using substr as the extension length is unknown (you can use substr & strrpos as well but php provides this functionality for you)
It's very easy to bypass as changing the extension of a file does not change the contents of the file. So a .exe renamed into a .jpg is still an .exe waiting to be run anyway. You can use it for a basic check, but don't rely solely on it to validate file types.
This substr() call:
substr($name, strlen($name) -3, 3)
Is better more simply written as:
substr($name, -3)
Which PHP just interprets as 'take only the last 3 characters of $name'.
EDIT: it's not better per se because file extensions don't necessarily have to be 3 characters long. They could be 2, they could be 4, 5, even 10. This is why as I said, checking file extensions isn't very reliable.
I prefer to whitelist the Mimetypes I want to allow using something along the lines of
$mimesGeneral = array(
'txt'=>'text/plain',
'doc'=>'application/msword',
'pdf'=>'application/pdf',
'xls'=>'application/x-excel',
'xls'=>'application/excel',
'xls'=>'application/vnd.ms-excel',
'rtf'=>'application/rtf',
'zip'=>'application/zip'
);
$success = false;
foreach($allowedMimes as $key=>$value){
if($_FILES['uploaded_file']['type'] == $value){
return true;
}
}
I use this with a blacklist of suffixes e.g 'php', 'pl', 'exe' etc...
People will still be able to upload whatever they want; they just have to give the file a particular extension.
For substr, see the manual.
Related
First of all I would like to mention that I'm a complete noob in php and since I'm playing around with wordpress based websites I started fiddling with PHP code. I have absolutely no background in php and most of my knowledge comes from reading code and using php.net reference to undestand the causal relationships and generally what is happening.
I have come up with a solution for a problem that I have, however I feel that solution is ugly and could be optimized to be more readable and more efficient. I'm asking this question primarily as mean to learn from the answers, so if you honor me with a reply it will be greatly appreciated if you throw in a few lines of argumentation as to why this optimization is viable.
The goal of the code is to take the contents of $image_path which is just a filename of an image that has either jpg, jpeg or png file extension and rewrite that filename by adding -200x100 just before the extension(foo.jpg turns into foo-200x100.jpg). Here is the code I came up with:
if (strpos($image_path, '.jpeg')) {
$image_thumb = substr_replace($image_path, '-200x100.jpeg', strpos($image_path, '.jpeg'));
}
elseif (strpos($image_path, '.jpg')) {
$image_thumb = substr_replace($image_path, '-200x100.jpg', strpos($image_path, '.jpg'));
}
elseif (strpos($image_path, '.png')) {
$image_thumb = substr_replace($image_path, '-200x100.png', strpos($image_path, '.png'));
}
else {
$image_thumb = 'No image';
}
Your solution matches the extension in any part of the path, which is probably not what you want:
$image_path = '/some/path/images.jpeg/this-is-an-image.jpeg';
Result:
/some/path/images-200x100.jpeg
You can use pathinfo() to parse $image_path into its components and then put them back together with modifications:
$pathinfo = pathinfo($image_path);
if (isset($pathinfo['extension']) && in_array($pathinfo['extension'], array('jpeg', 'jpg', 'png'), true)) {
$image_thumb = sprintf('%s/%s-200x100.%s', $pathinfo['dirname'], $pathinfo['filename'], $pathinfo['extension']);
} else {
$image_thumb = 'No image';
}
Result for the previous example:
/some/path/images.jpeg/this-is-an-image-200x100.jpeg
I am currently in the process of writing a mobile app with the help of phonegap. One of the few features that I would like this app to have is the ability to capture an image and upload it to a remote server...
I currently have the image capturing and uploading/emailing portion working fine with a compiled apk... but in my php, I am currently naming the images "image[insert random number from 10 to 20]... The problem here is that the numbers can be repeated and the images can be overwritten... I have read and thought about just using rand() and selecting a random number from 0 to getrandmax(), but i feel that I might have the same chance of a file overwriting... I need the image to be uploaded to the server with a unique name every-time, no matter what... so the php script would check to see what the server already has and write/upload the image with a unique name...
any ideas other than "rand()"?
I was also thinking about maybe naming each image... img + date + time + random 5 characters, which would include letters and numbers... so if an image were taken using the app at 4:37 am on March 20, 2013, the image would be named something like "img_03-20-13_4-37am_e4r29.jpg" when uploaded to the server... I think that might work... (unless theres a better way) but i am fairly new to php and wouldn't understand how to write something like that...
my php is as follows...
print_r($_FILES);
$new_image_name = "image".rand(10, 20).".jpg";
move_uploaded_file($_FILES["file"]["tmp_name"], "/home/virtual/domain.com/public_html/upload/".$new_image_name);
Any help is appreciated...
Thanks in advance!
Also, Please let me know if there is any further info I may be leaving out...
You may want to consider the PHP's uniqid() function.
This way the code you suggested would look like the following:
$new_image_name = 'image_' . date('Y-m-d-H-i-s') . '_' . uniqid() . '.jpg';
// do some checks to make sure the file you have is an image and if you can trust it
move_uploaded_file($_FILES["file"]["tmp_name"], "/home/virtual/domain.com/public_html/upload/".$new_image_name);
Also keep in mind that your server's random functions are not really random. Try random.org if you need something indeed random. Random random random.
UPD: In order to use random.org from within your code, you'll have to do some API requests to their servers. The documentation on that is available here: www.random.org/clients/http/.
The example of the call would be: random.org/integers/?num=1&min=1&max=1000000000&col=1&base=10&format=plain&rnd=new. Note that you can change the min, max and the other parameters, as described in the documentation.
In PHP you can do a GET request to a remote server using the file_get_contents() function, the cURL library, or even sockets. If you're using a shared hosting, the outgoing connections should be available and enabled for your account.
$random_int = file_get_contents('http://www.random.org/integers/?num=1&min=1&max=1000000000&col=1&base=10&format=plain&rnd=new');
var_dump($random_int);
You should use tempnam() to generate a unique file name:
// $baseDirectory Defines where the uploaded file will go to
// $prefix The first part of your file name, e.g. "image"
$destinationFileName = tempnam($baseDirectory, $prefix);
The extension of your new file should be done after moving the uploaded file, i.e.:
// Assuming $_FILES['file']['error'] == 0 (no errors)
if (move_uploaded_file($_FILES['file']['tmp_name'], $destinationFileName)) {
// use extension from uploaded file
$fileExtension = '.' . pathinfo($_FILES['file']['name'], PATHINFO_EXTENSION);
// or fix the extension yourself
// $fileExtension = ".jpg";
rename($destinationFileName, $destinationFileName . $fileExtension);
} else {
// tempnam() created a new file, but moving the uploaded file failed
unlink($destinationFileName); // remove temporary file
}
Have you considered using md5_file ?
That way all of your files will have unique name and you would not have to worry about duplicate names. But please note that this will return same string if the contents are the same.
Also here is another method:
do {
$filename = DIR_UPLOAD_PATH . '/' . make_string(10) . '-' . make_string(10) . '-' . make_string(10) . '-' . make_string(10);
} while(is_file($filename));
return $filename;
/**
* Make random string
*
* #param integer $length
* #param string $allowed_chars
* #return string
*/
function make_string($length = 10, $allowed_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890') {
$allowed_chars_len = strlen($allowed_chars);
if($allowed_chars_len == 1) {
return str_pad('', $length, $allowed_chars);
} else {
$result = '';
while(strlen($result) < $length) {
$result .= substr($allowed_chars, rand(0, $allowed_chars_len), 1);
} // while
return $result;
} // if
} // make_string
Function will create a unique name before uploading image.
// Upload file with unique name
if ( ! function_exists('getUniqueFilename'))
{
function getUniqueFilename($file)
{
if(is_array($file) and $file['name'] != '')
{
// getting file extension
$fnarr = explode(".", $file['name']);
$file_extension = strtolower($fnarr[count($fnarr)-1]);
// getting unique file name
$file_name = substr(md5($file['name'].time()), 5, 15).".".$file_extension;
return $file_name;
} // ends for is_array check
else
{
return '';
} // else ends
} // ends
}
I'm building a class that returns an string that will include files in a folder automatically, kinda like a loader for an HTML file.
Here's the method that will be called:
function build_external_file_include($_dir){
$_files = scandir($_dir);
$_stack_of_file_includes = "";//will store the includes for the specified file.
foreach ($_files as $ext){
//split the file name into two;
$fileName = explode('.',$ext, 1);//this is where the issue is.
switch ($fileName[1]){
case "js":
//if file is javascript
$_stack_of_file_includes = $_stack_of_file_includes."<script type='text/javascript' src='".$dir.'/'. $ext ."'></script>";
break;
case "css";//if file is css
$_stack_of_file_includes = $_stack_of_file_includes."<link rel=\"stylesheet\" type=\"text/css\" href=\"".$dir.'/'. $ext."\" />";
break;
default://if file type is unkown
$_stack_of_file_includes = $_stack_of_file_includes."<!-- File: ". $ext." was not included-->";
}
}
return $_stack_of_file_includes;
}
So, this runs without any errors. But, it doesn't do what it's supposed to do... or at least what I intend it to do. Technically speaking here,
$fileName[1] should be the extension js
$fileName[0] should be name of the file main
but
$fileName[0] is main.js.
does explode not recognize .?
Thank you in advance.
You're forcing your resulting array to have 1 element, which causes it to have the entire filename.
explode( '.', $ext, 1 )
should instead be
explode( '.', $ext );
Proof: http://codepad.org/01DLpo6H
You've limited the explode to producing 1 array entry, so it can never do anything:
print_r(explode('.', 'a.b', 1));
Array
(
[0] => a.b
)
The limit should be at least 2. or, better yet, you should be using the pathinfo() function, which properly handles filename components for you.
I try mime_content_type() / finfo_open(). It is ok for .doc but return 'application/zip' for .docx and nothing for .xls
what is the problem ? is it a issue with my browser?
This question is basically the same: PHP 5.3.5 fileinfo() MIME Type for MS Office 2007 files - magic.mime updates?
And it seems there is no solution. It's not your browser, it's a mime "magic" file that tried to guess, and there is no way to tell the different between docx and a zipfile because docx IS in fact a zipfile!
If you're like me and may or may not be using php>=5.3.0 servers for whatever reason and want to use one set of code for all servers, and perhaps insist on involving the mime_content_type function somehow for servers that do not have Fileinfo, then you can use a half-arsed solution like mine, which is to make a replacement function which is that on php>=5.3.0 it uses Fileinfo, and on lower versions, if the filename ends in a specific string unique to something you want to override, it returns your hardcoded value, and calls mime_content_type() for all other types. However, of course this will NOT work if the file is of a type that mime_content_type() incorrectly detects and where the file name does not end in an extension, but that should be very rare.
Such a solution might look something like this:
function _mime_content_type($filename)
{
//mime_content_type replacement that uses Fileinfo native to php>=5.3.0
if( phpversion() >= '5.3.0' )
{
$result = new finfo();
if (is_resource($result) === true)
{
return $result->file($filename, FILEINFO_MIME_TYPE);
}
}
else
{
if( substr( $filename, -5, 5 ) == '.docx' )
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
else if( substr( $filename, -5, 5 ) == '.xlsx' )
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
else if( substr( $filename, -5, 5 ) == '.pptx' )
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.presentation';
//amend this with manual overrides to your heart's desire
return mime_content_type( $filename );
}
}
and then you simply have to replace all your calls to mime_content_type with calls to _mime_content_type.
$dir_handle = #opendir($url) or die("Unable to open $url");
$count = "0";
while ($file = readdir($dir_handle)) {
if (!is_dir($url.'/'.$file) && ($file="*.jpg" || $file="*.gif" || $file="*.png") && $file!="picture0.*") {
$galleryEventFile[$count] = $file;
$count++;
}
}
closedir($dir_handle);
I think it has something to do with this line:
if (!is_dir($url.'/'.$file) && ($file="*.jpg" || $file="*.gif" || $file="*.png") && $file!="picture0.*")
but im not sure
I can see two things that will be causing you problems:
Assignment/comparison:
You have the code:
if ($file="*.jpg" //etc...
However, a single equal sign will perform an assignment, not a comparison - you need to use two equals signs (==) for this. See http://php.net/manual/en/language.operators.comparison.php. Essentially what you are doing by doing an assignment in an if statement is:
$file = '*.jpg';
if ($file) { }
Wildcard matching of strings
You also can't do wildcard matching like that ($file == "*.jpg) on a string, you could look at using preg_match() and regular expressions instead, e.g.
if (!preg_match('/\.jpg$/i', $file)) {
//not .jpg
}
It might be better to do something like this though:
//get file extension
$extension = pathinfo($file, PATHINFO_EXTENSION);
$allowedExtensions = array('jpg', 'png', 'gif');
//check in allowed list
if (!in_array(strtolower($extension), $allowedExtensions)) {
//not valid
}
First, $count should be a number. Do:
$count = 0;
Second, AFAIK, PHP doesn't support wildcard matching like that. You can't use "*" to match. You'll need to use regular expressions to match in the conditional.
Do as thedz and Tom Haigh have suggested.
Have you also heard about XDebug? This will allow you to setup an environment say using Eclipse and step through your PHP code. I do not develop without using a combination Eclipse and XDebug.
The first thing you want to do is to debug the if line. Remember that if you put *.gif, it is looking to see that the file is actually named "*.gif", rather than looking for 'any' gif file, similar to what Windows does.
What I'd suggest is going through each segment of the if, and get it to pass. then you can start putting it together.