I am working on a PHP upload script which allows uploading only PDF files. I am checking for the application/pdf MIME type before uploading. I have a PDF from my client that I am testing the upload script with and it is failing the upload error check and telling me that the file is a MIME type of application/download. I did download the PDF from the client via GMail. I also tested this with a PDF that I created in Photoshop and the script tells me that it is also of application/download type.
Here is how I am checking:
$mimeType = 'application/pdf';
if($_FILES[$filesName]['type'] != $mimeType)
{
throw new UploadErrorException('File is not of correct type.');
}
I am trying to get the Fileinfo extension installed on my server (per grunk's advice), but for now I am looking for something to work without it.
Any ideas? Thanks!
Your upload script allows any file type as long as the client says it's PDF. Some browsers can not determine MIME types (duh, since determining MIME types is a hard problem) and just send a generic one. The correct way to check for the "real" MIME type is to use fileinfo.
If you don't have fileinfo, use the following drop-in replacement (PDF only):
if (!class_exists('finfo')) {
class finfo {
function buffer($string) {
switch (substr($string, 0, 4)) {
case '%PDF': return 'application/pdf';
default: return 'application/binary';
}
}
function file($file_name, $options=0, $context=NULL) {
$f = fopen($file_name, 'rb', false, $context);
if ($f === false) return false;
$magic = fread($f, 4);
fclose($f);
return $this->buffer($magic);
}
}
}
$finfo = new finfo();
echo $finfo->file('test.pdf');
If you can't use fileinfo for any reason you can open the uploaded file and check first byte in it.
As you want only pdf file it's pretty easy. Open a pdf file with a text editor and you probably can see something like
%PDF-1.5
So if the uploaded file contain %PDF , have a .pdf extension and application/pdf as mime type, you have good chance to deal with the good type.
But as i already said , if you can use fileinfo it's a better solution
Related
I'm developing a simple php upload script, and users can upload only ZIP and RAR files.
What MIME types I should use to check $_FILES[x][type]? (a complete list please)
The answers from freedompeace, Kiyarash and Sam Vloeberghs:
.rar application/vnd.rar, application/x-rar-compressed, application/octet-stream
.zip application/zip, application/octet-stream, application/x-zip-compressed, multipart/x-zip
I would do a check on the file name too. Here is how you could check if the file is a RAR or ZIP file. I tested it by creating a quick command line application.
<?php
if (isRarOrZip($argv[1])) {
echo 'It is probably a RAR or ZIP file.';
} else {
echo 'It is probably not a RAR or ZIP file.';
}
function isRarOrZip($file) {
// get the first 7 bytes
$bytes = file_get_contents($file, FALSE, NULL, 0, 7);
$ext = strtolower(substr($file, - 4));
// RAR magic number: Rar!\x1A\x07\x00
// http://en.wikipedia.org/wiki/RAR
if ($ext == '.rar' and bin2hex($bytes) == '526172211a0700') {
return TRUE;
}
// ZIP magic number: none, though PK\003\004, PK\005\006 (empty archive),
// or PK\007\008 (spanned archive) are common.
// http://en.wikipedia.org/wiki/ZIP_(file_format)
if ($ext == '.zip' and substr($bytes, 0, 2) == 'PK') {
return TRUE;
}
return FALSE;
}
Notice that it still won't be 100% certain, but it is probably good enough.
$ rar.exe l somefile.zip
somefile.zip is not RAR archive
But even WinRAR detects non RAR files as SFX archives:
$ rar.exe l somefile.srr
SFX Volume somefile.srr
For upload:
An official list of mime types can be found at The Internet Assigned Numbers Authority (IANA) . According to their list Content-Type header for zip is application/zip.
The media type for rar files, registered at IANA in 2016, is application/vnd.rar (see https://www.iana.org/assignments/media-types/application/vnd.rar). The mime-type value, application/x-rar-compressed, was used before that and is still commonly used even though it is marked as deprecated in the document above.
application/octet-stream means as much as: "I send you a file stream and the content of this stream is not specified" (so it is true that it can be a zip or rar file as well). The server is supposed to detect what the actual content of the stream is.
Note: For upload it is not safe to rely on the mime type set in the Content-Type header. The header is set on the client and can be set to any random value. Instead you can use the php file info functions to detect the file mime-type on the server.
For download:
If you want to download a zip file and nothing else you should only set one single Accept header value. Any additional values set will be used as a fallback in case the server cannot satisfy your in the Accept header requested mime-type.
According to the WC3 specifications this:
application/zip, application/octet-stream
will be intrepreted as: "I prefer a application/zip mime-type, but if you cannot deliver this an application/octet-stream (a file stream) is also fine".
So only a single:
application/zip
Will guarantee you a zip file (or a 406 - Not Acceptable response in case the server is unable to satisfy your request).
You should not trust $_FILES['upfile']['mime'], check MIME type by yourself. For that purpose, you may use fileinfo extension, enabled by default as of PHP 5.3.0.
$fileInfo = new finfo(FILEINFO_MIME_TYPE);
$fileMime = $fileInfo->file($_FILES['upfile']['tmp_name']);
$validMimes = array(
'zip' => 'application/zip',
'rar' => 'application/x-rar',
);
$fileExt = array_search($fileMime, $validMimes, true);
if($fileExt != 'zip' && $fileExt != 'rar')
throw new RuntimeException('Invalid file format.');
NOTE: Don't forget to enable the extension in your php.ini and restart your server:
extension=php_fileinfo.dll
I see many answer reporting for zip and rar the Media Types application/zip and application/x-rar-compressed, respectively.
While the former matching is correct, for the latter IANA reports here https://www.iana.org/assignments/media-types/application/vnd.rar that for rar application/x-rar-compressed is a deprecated alias name and instead application/vnd.rar is the official one.
So, right Media Types from IANA in 2020 are:
zip: application/zip
rar: application/vnd.rar
In a linked question, there's some Objective-C code to get the mime type for a file URL. I've created a Swift extension based on that Objective-C code to get the mime type:
import Foundation
import MobileCoreServices
extension URL {
var mimeType: String? {
guard self.pathExtension.count != 0 else {
return nil
}
let pathExtension = self.pathExtension as CFString
if let preferredIdentifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil) {
guard let mimeType = UTTypeCopyPreferredTagWithClass(preferredIdentifier.takeRetainedValue(), kUTTagClassMIMEType) else {
return nil
}
return mimeType.takeRetainedValue() as String
}
return nil
}
}
As extension might contain more or less that three characters the following will test for an extension regardless of the length of it.
Try this:
$allowedExtensions = array( 'mkv', 'mp3', 'flac' );
$temp = explode(".", $_FILES[$file]["name"]);
$extension = strtolower(end($temp));
if( in_array( $extension, $allowedExtensions ) ) { ///
to check for all characters after the last '.'
I'd like to check if an uploaded file is an image file (e.g png, jpg, jpeg, gif, bmp) or another file. The problem is that I'm using Uploadify to upload the files, which changes the mime type and gives a 'text/octal' or something as the mime type, no matter which file type you upload.
Is there a way to check if the uploaded file is an image apart from checking the file extension using PHP?
My thought about the subject is simple: all uploaded images are evil.
And not only because they can contain malicious codes, but particularly because of meta-tags. I'm aware about crawlers that browse the web to find some protected images using their hidden meta-tags, and then play with their copyright. Perhaps a bit paranoid, but as user-uploaded images are out of control over copyright issues, I take it seriousely into account.
To get rid of those issues, I systematically convert all uploaded images to png using gd. This have a lot of advantages: image is clean from eventual malicious codes and meta tags, I only have one format for all uploaded images, I can adjust the image size to fit with my standard, and... I immediately know if the image is valid or not! If the image can't be opened for conversion (using imagecreatefromstring which doesn't care about image format), then I consider the image as invalid.
A simple implementation could look like this:
function imageUploaded($source, $target)
{
// check for image size (see #DaveRandom's comment)
$size = getimagesize($source);
if ($size === false) {
throw new Exception("{$source}: Invalid image.");
}
if ($size[0] > 2000 || $size[1] > 2000) {
throw new Exception("{$source}: Too large.");
}
// loads it and convert it to png
$sourceImg = #imagecreatefromstring(#file_get_contents($source));
if ($sourceImg === false) {
throw new Exception("{$source}: Invalid image.");
}
$width = imagesx($sourceImg);
$height = imagesy($sourceImg);
$targetImg = imagecreatetruecolor($width, $height);
imagecopy($targetImg, $sourceImg, 0, 0, 0, 0, $width, $height);
imagedestroy($sourceImg);
imagepng($targetImg, $target);
imagedestroy($targetImg);
}
To test it:
header('Content-type: image/png');
imageUploaded('http://www.dogsdata.com/wp-content/uploads/2012/03/Companion-Yellow-dog.jpg', 'php://output');
This does not exactly answer your question as this is the same kind of hack than the accepted answer, but I give you my reasons to use it, at least :-)
You could use getimagesize() which returns zeros for size on non-images.
You can verify the image type by checking for magic numbers at the beginning of the file.
For example: Every JPEG file begins with a "FF D8 FF E0" block.
Here is more info on magic numbers
If Uploadify really changes the mime type - i would consider it a bug.
It doesn't make sense at all, because that blocks developers from working with
mime-type based functions in PHP:
finfo_open()
mime_content_type()
exif_imagetype().
This is a little helper function which returns the mime-type based on the first 6 bytes of a file.
/**
* Returns the image mime-type based on the first 6 bytes of a file
* It defaults to "application/octet-stream".
* It returns false, if problem with file or empty file.
*
* #param string $file
* #return string Mime-Type
*/
function isImage($file)
{
$fh = fopen($file,'rb');
if ($fh) {
$bytes = fread($fh, 6); // read 6 bytes
fclose($fh); // close file
if ($bytes === false) { // bytes there?
return false;
}
// ok, bytes there, lets compare....
if (substr($bytes,0,3) == "\xff\xd8\xff") {
return 'image/jpeg';
}
if ($bytes == "\x89PNG\x0d\x0a") {
return 'image/png';
}
if ($bytes == "GIF87a" or $bytes == "GIF89a") {
return 'image/gif';
}
return 'application/octet-stream';
}
return false;
}
You can check the first few bytes of the file for the magic number to figure out the image format.
Try using exif_imagetype to retrieve the actual type of the image. If the file is too small it will throw an error and if it can't find it it will return false
Is it not possible to interrogate the file with finfo_file?
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimetype = finfo_file($finfo, $filename); //should contain mime-type
finfo_close($finfo);
This answer is untested but based on this forum discussion on the Uploadify forums.
I would also point out that finfo should "try to guess the content type and encoding of a file by looking for certain magic byte sequences at specific positions within the file" so in my mind this should still work even though Uploadify has specified the wrong mime type.
I need to make a website that will allow registered users to upload audio files.
I wonder is there any bullet proof practice regarding security.
The site is built in PHP
Check mime type of uploading file
mp3 -> audio/mpeg
More here: http://www.w3schools.com/media/media_mimeref.asp
You will want to check the file type carefully. This means not just doing a substring on the file name to get the extension. The extension is not a concrete indicator of what the file actually is.
As Danzan said, you will want to check the MIME type of the file, using some code like this:
if ($_FILES["audioUpload"]["type"] == "audio/mpeg") {
//proceed with upload procedure
} else {
echo "Only mp3's are allowed to be uploaded.";
}
This reduces the chances of a user uploading, say, malicious PHP code into your upload directory to basically zero.
Bullet-proof file type check is provided via combination of getimagesize, fileinfo extension and mime_content_type function (Nette Framework property):
// $file is absolute path to the uploaded file
$info = #getimagesize($file); // # - files smaller than 12 bytes causes read error
if (isset($info['mime'])) {
return $info['mime'];
} elseif (extension_loaded('fileinfo')) {
$type = preg_replace('#[\s;].*$#', '', finfo_file(finfo_open(FILEINFO_MIME), $file));
} elseif (function_exists('mime_content_type')) {
$type = mime_content_type($file);
}
return isset($type) && preg_match('#^\S+/\S+$#', $type)
? $type
: 'application/octet-stream';
You can not trust any data coming from the client, because they can be easily forged.
You can upload anything with PHP. Here's an example: http://www.tizag.com/phpT/fileupload.php
Regarding security, you have to verify that only certain people are allowed to upload stuff and that you verify the contents of what they're uploading (file size, file type, etc).
I am working on a project that requires file uploads. I want to make sure it's secure and that only PDF files are being uploaded. I am already checking file extension but i want to make sure the file really is a pdf file.
What is the best way to check the mime type in php? I am using PHP 5.2.5 and can't seem to get fileinfo or mime_content_type() working.
for fileinfo i keep getting this:
Warning: finfo_open() [function.finfo-open]: Failed to load magic database
at '(null)'. in [snipped filename] on line 35
mime types are not reliable for checking type of files. A client's browser might report it wrongly.
Check for Magic Number. PDF files start with "%PDF" (25 50 44 46).
Indeed, MIME type's aren't the best way of making sure that a user has uploaded a valid file since this can be easily faked if you know how.
But when the file is being posted, you can always check the mime type this way:
$Type = $_FILES['someFile']['type'];
Maybe you could use a php class to determine it's a valid PDF something like FPDF( http://www.fpdf.org/)
Well, good luck anyways :)
It probably means that the MAGIC environment variable is not set, and your magic file isn't at /usr/share/misc/magic . Either set the value of MAGIC to point to the correct magic file, or pass the magic file as a second parameter to your finfo constructor
$finfo = new finfo(FILEINFO_MIME, "/usr/share/misc/magic");
or
$finfo = finfo_open(FILEINFO_MIME, "/usr/share/misc/magic");
An easy way to get the MIME type is directly from $_FILES.
If the mime type contains the word 'pdf' then you can consider it a valid PDF.
$contentType = $_FILES['myFile']['type'];
if(isValidPDF($contentType)) {
die('It is a PDF');
} else {
die('It is not a PDF');
}
function isValidPDF($mime) {
return strpos($mime, 'pdf') !== false;
}
I have an index.php file which has to process many different file types. How do I guess the filetype based on the REQUEST_URI?
If I request http://site/image.jpg, and all requests redirect through index.php, which looks like this
<?php
include('/www/site'.$_SERVER['REQUEST_URI']);
?>
How would I make that work correctly?
Should I test based on the extension of the file requested, or is there a way to get the filetype?
If you are sure you're only ever working with images, you can check out the exif_imagetype() PHP function, which attempts to return the image MIME type.
If you don't mind external dependencies, you can also check out the excellent getID3 library which can determine the MIME type of many different file types.
Lastly, you can check out the mime_content_type() function - but it has been deprecated for the Fileinfo PECL extension.
mime_content_type() is deprecated, so you won't be able to count on it working in the future. There is a "fileinfo" PECL extension, but I haven't heard good things about it.
If you are running on a Unix-like server, you can do the following, which has worked fine for me:
$file = escapeshellarg($filename);
$mime = shell_exec("file -bi " . $file);
$filename should probably include the absolute path.
function get_mime($file) {
if (function_exists("finfo_file")) {
$finfo = finfo_open(FILEINFO_MIME_TYPE); // Return MIME type a la the 'mimetype' extension
$mime = finfo_file($finfo, $file);
finfo_close($finfo);
return $mime;
} else if (function_exists("mime_content_type")) {
return mime_content_type($file);
} else if (!stristr(ini_get("disable_functions"), "shell_exec")) {
// http://stackoverflow.com/a/134930/1593459
$file = escapeshellarg($file);
$mime = shell_exec("file -bi " . $file);
return $mime;
} else {
return false;
}
}
For me, nothing of this works—mime_content_type is deprecated, finfo is not installed, and shell_exec is not allowed.
I actually got fed up by the lack of standard MIME sniffing methods in PHP. Install fileinfo... Use deprecated functions... Oh, these work, but only for images! I got fed up of it, so I did some research and found the WHATWG MIME sniffing specification - I believe this is still a draft specification though.
Anyway, using this specification, I was able to implement a MIME sniffer in PHP. Performance is not an issue. In fact, on my humble machine, I was able to open and sniff thousands of files before PHP timed out.
Here is the MimeReader class.
require_once("MimeReader.php");
$mime = new MimeReader(<YOUR FILE PATH>);
$mime_type_string = $mime->getType(); // "image/jpeg", etc.
If you are working with images only and you need a MIME type (e.g., for headers), then this is the fastest and most direct answer:
$file = 'path/to/image.jpg';
$image_mime = image_type_to_mime_type(exif_imagetype($file));
It will output true image MIME type even if you rename your image file.
You can use finfo to accomplish this as of PHP 5.3:
<?php
$info = new finfo(FILEINFO_MIME_TYPE);
echo $info->file('myImage.jpg');
// prints "image/jpeg"
The FILEINFO_MIME_TYPE flag is optional; without it you get a more verbose string for some files; (apparently some image types will return size and colour depth information). Using the FILEINFO_MIME flag returns the mime-type and encoding if available (e.g. image/png; charset=binary or text/x-php; charset=us-ascii). See this site for more info.
According to the PHP manual, the finfo-file function is best way to do this. However, you will need to install the FileInfo PECL extension.
If the extension is not an option, you can use the outdated mime_content_type function.
mime_content_type() appears to be the way to go, notwithstanding the previous comments saying it is deprecated. It is not -- or at least this incarnation of mime_content_type() is not deprecated, according to http://php.net/manual/en/function.mime-content-type.php. It is part of the FileInfo extension, but the PHP documentation now tells us it is enabled by default as of PHP 5.3.0.
If you run Linux and have the extension you could simply read the MIME type from /etc/mime.types by making a hash array. You can then store that in memory and simply call the MIME by array key :)
/**
* Helper function to extract all mime types from the default Linux /etc/mime.types
*/
function get_mime_types() {
$mime_types = array();
if (
file_exists('/etc/mime.types') &&
($fh = fopen('/etc/mime.types', 'r')) !== false
) {
while (($line = fgets($fh)) !== false) {
if (!trim($line) || substr($line, 0, 1) === '#') continue;
$mime_type = preg_split('/\t+/', rtrim($line));
if (
is_array($mime_type) &&
isset($mime_type[0]) && $mime_type[0] &&
isset($mime_type[1]) && $mime_type[1]
) {
foreach (explode(' ', $mime_type[1]) as $ext) {
$mime_types[$ext] = $mime_type[0];
}
}
}
fclose($fh);
}
return $mime_types;
}
I haven't used it, but there's a PECL extension for getting a file's MIME type. The official documentation for it is in the manual.
Depending on your purpose, a file extension can be ok, but it's not incredibly reliable since it's so easily changed.
If you're only dealing with images, you can use the [getimagesize()][1] function which contains all sorts of information about the image, including the type.
A more general approach would be to use the FileInfo extension from PECL.
Some people have serious complaints about that extension... so if you run into serious issues or cannot install the extension for some reason you might want to check out the deprecated function mime_content_type().
The MIME type of any file on your server can be gotten with this:
<?php
function get_mime($file_path){
$finfo = new finfo(FILEINFO_MIME_TYPE);
$type = $finfo->file(file_path);
}
$mime = get_mime('path/to/file.ext');
I got very good results using a user function from
http://php.net/manual/de/function.mime-content-type.php
#''john dot howard at prismmg dot com 26-Oct-2009 03:43''
function get_mime_type($filename, $mimePath = '../etc') { ...
which doesn’t use finfo, exec or a deprecated function.
It works well also with remote resources!