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 '.'
Related
I have a tool which allows a user to upload a spreadsheet, and then I parse the spreadsheet with Laravel-Excel. My issue is, how can I check that the file is a valid excel file before attempting to parse it?
I looked in the PHPOffice/Laravel-Excel docs, and could not find a method for validating a file.
So, my next guess was, if I attempt to Load() an invalid file, it will bomb out and give me a warning or error. However, rather than doing that, it will parse the file and try to somehow convert it to a spreadsheet. For example, I fed it a pdf and it did generate a collection containing whatever non-binary junk it could find in the pdf file. This is not desirable.
Currently, I am doing a mime-type check to validate the file.
//valid mime types
$mimeTypes = [
'application/csv', 'application/excel',
'application/vnd.ms-excel', 'application/vnd.msexcel',
'text/csv', 'text/anytext', 'text/plain', 'text/x-c',
'text/comma-separated-values',
'inode/x-empty',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
$file = request()->hasFile('file');
if ($file) {
if (in_array(request()->file('file')->getClientMimeType(), $mimeTypes)) {
//then parse the file
Config::set('excel.import.heading', 'original');
$data = Excel::load(request()->file('file')->path(), function ($reader) {
})->get();
//do some stuff with data...
} else {
//invalid file
}
} else {
//no file uploaded
}
This is not ideal, since there seems to be an exotic variety of possible mime types, so I would have to actively maintain the list, and certain csv files have a plaintext mime-type, so non-csv-plaintext files would pass muster here. Is there any standard way, provided by either Laravel, Laravel-Excel, or PHPOffice, to validate the file?
This is not a MIME check on the file!
You are only checking the user-supplied file extension information here.
string|null getClientMimeType()
Returns the file mime type.
The client mime type is extracted from the request from which the file was uploaded, so it should not be considered as a safe value.
For a trusted mime type, use getMimeType() instead (which guesses the mime type based on the file content).
Symfony Component HttpFoundation File UploadedFile
If you want to validate the magic MIME bytes in the uploaded file itself you can do this by relying on your operating system's magic MIME byte file like this in PHP.
$finfo = finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
// $mime will contain the correct mime type
$mime = finfo_file($finfo, $_FILES['your-form-upload-input-name-here']['tmp_name'];
finfo_close($finfo);
See finfo_file for more details.
To do this specifically using the Symfony Component you're currently relying on call the getMimeType() method instead. Note the difference is that getClientMimeType() uses the client-supplied information which can't be trusted, whereas getMimeType() does the same thing as demonstrated with finfo_file() above.
I've read something about MIME and finfo() but I didn't understand!
Also I tried these:
if ($_FILES["uploadedFile"]["type"] == "text/docx"/*or == document/docx*/)
echo "1";
It is obviously that it won't work.
When I echo $_FILES['uploadedFile']['type']; then I saw this for a word document(My server is Linux):
application/vnd.openxmlformats-officedocument.wordprocessingml.document
So must I use: ?
if ($_FILES["uploadedFile"]["type"] == "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
What is an easy and reliable way to find file extension(Not only Images)?
(I mean using some variables which are not filled on client side)
If you are using php 5.3, you can get true mime-type:
$finfo = finfo_open();
$file = $_FILES["uploadedFile"];
finfo_file($finfo, $file, FILEINFO_MIME_TYPE);
finfo_close($finfo);
More Info:
http://php.net/manual/en/function.finfo-open.php
How about
$file = $_FILES['uploadedFile'['name'];
$pathparts = pathinfo($file);
$ext = $pathparts['extension'];
This will give you the actual file extension to work with.
The best way to determine the file type is by examining the first few bytes of a file – referred to as “magic bytes”. Magic bytes are essentially signatures that vary in length between 2 to 40 bytes in the file headers, or at the end of a file.
your best bet is PECL extension called Fileinfo.
As of PHP 5.3, Fileinfo is shipped with the main distribution and is enabled by default
// in PHP 4 :
$fhandle = finfo_open(FILEINFO_MIME);
$mime_type = finfo_file($fhandle,$file); // e.g. gives for example "image/jpeg" for a jpeg file
// in PHP 5 you can do :
$file_info = new finfo(FILEINFO_MIME); // object oriented approach!
$mime_type = $file_info->buffer(file_get_contents($file));
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 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
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!