From 6faed0e451f5539a12d811cb677340a2ddf0c985 Mon Sep 17 00:00:00 2001 From: Mikael Nordfeldth Date: Sat, 8 Mar 2014 03:03:04 +0100 Subject: [PATCH] MediaFile loses dependency on PEAR::MIME At the same time we remove the "filecommand" setting, since we will likely not have use of it thanks to PECL fileinfo. Also the "supported" list for attachment mime types has changed format, so we can keep track of at least some known file extensions. --- CONFIGURE | 7 ---- lib/default.php | 64 ++++++++++++++++++------------------- lib/mediafile.php | 71 +++++++++++++++-------------------------- lib/util.php | 25 +++++++++++++++ tests/MediaFileTest.php | 8 ++--- 5 files changed, 87 insertions(+), 88 deletions(-) diff --git a/CONFIGURE b/CONFIGURE index 8abc0513a9..141c475563 100644 --- a/CONFIGURE +++ b/CONFIGURE @@ -549,10 +549,6 @@ supported: an array of mime types you accept to store and distribute, setup your server to properly recognize the types you want to support. uploads: false to disable uploading files with notices (true by default). -filecommand: The required MIME_Type library may need to use the 'file' - command. It tries the one in the Web server's path, but if - you're having problems with uploads, try setting this to the - correct value. Note: 'file' must accept '-b' and '-i' options. For quotas, be sure you've set the upload_max_filesize and post_max_size in php.ini to be large enough to handle your upload. In httpd.conf @@ -577,9 +573,6 @@ path: URL path, relative to the server, to find files. Defaults to main path + '/file/'. ssl: whether to use HTTPS for file URLs. Defaults to null, meaning to guess based on other SSL settings. -filecommand: command to use for determining the type of a file. May be - skipped if fileinfo extension is installed. Defaults to - '/usr/bin/file'. sslserver: if specified, this server will be used when creating HTTPS URLs. Otherwise, the site SSL server will be used, with /file/ path. sslpath: if this and the sslserver are specified, this path will be used diff --git a/lib/default.php b/lib/default.php index 3a28ec12c7..2fd660e2ad 100644 --- a/lib/default.php +++ b/lib/default.php @@ -213,42 +213,42 @@ $default = 'sslserver' => null, 'sslpath' => null, 'ssl' => null, - 'supported' => array('image/png', - 'image/jpeg', - 'image/gif', - 'image/svg+xml', - 'audio/mpeg', - 'audio/x-speex', - 'application/ogg', - 'application/pdf', - 'application/vnd.oasis.opendocument.text', - 'application/vnd.oasis.opendocument.text-template', - 'application/vnd.oasis.opendocument.graphics', - 'application/vnd.oasis.opendocument.graphics-template', - 'application/vnd.oasis.opendocument.presentation', - 'application/vnd.oasis.opendocument.presentation-template', - 'application/vnd.oasis.opendocument.spreadsheet', - 'application/vnd.oasis.opendocument.spreadsheet-template', - 'application/vnd.oasis.opendocument.chart', - 'application/vnd.oasis.opendocument.chart-template', - 'application/vnd.oasis.opendocument.image', - 'application/vnd.oasis.opendocument.image-template', - 'application/vnd.oasis.opendocument.formula', - 'application/vnd.oasis.opendocument.formula-template', - 'application/vnd.oasis.opendocument.text-master', - 'application/vnd.oasis.opendocument.text-web', - 'application/x-zip', - 'application/zip', - 'text/plain', - 'video/mpeg', - 'video/mp4', - 'video/quicktime', - 'video/webm'), + 'supported' => array( + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/pdf' => 'pdf', + 'application/zip' => 'zip', + 'image/png' => 'png', + 'image/jpeg' => 'jpg', + 'image/gif' => 'gif', + 'image/svg+xml' => 'svg', + 'image/vnd.microsoft.icon' => 'ico', + 'audio/ogg' => 'ogg', + 'audio/mpeg' => 'mpg', + 'audio/x-speex' => 'spx', + 'application/ogg' => 'ogx', + 'text/plain' => 'txt', + 'video/mpeg' => 'mpeg', + 'video/mp4' => 'mp4', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'mov', + 'video/webm' => 'webm', + ), 'file_quota' => 5000000, 'user_quota' => 50000000, 'monthly_quota' => 15000000, 'uploads' => true, - 'filecommand' => '/usr/bin/file', 'show_thumbs' => true, // show thumbnails in notice lists for uploaded images, and photos and videos linked remotely that provide oEmbed info 'thumb_width' => 100, 'thumb_height' => 75, diff --git a/lib/mediafile.php b/lib/mediafile.php index a2cb21ea45..c2da82caa8 100644 --- a/lib/mediafile.php +++ b/lib/mediafile.php @@ -228,7 +228,7 @@ class MediaFile // Throws exception if additional size does not respect quota File::respectsQuota($scoped, $_FILES[$param]['size']); - $mimetype = MediaFile::getUploadedFileType($_FILES[$param]['tmp_name'], + $mimetype = self::getUploadedMimeType($_FILES[$param]['tmp_name'], $_FILES[$param]['name']); $basename = basename($_FILES[$param]['name']); @@ -252,7 +252,7 @@ class MediaFile File::respectsQuota($scoped, filesize($stream['uri'])); - $mimetype = MediaFile::getUploadedFileType($stream['uri']); + $mimetype = self::getUploadedMimeType($stream['uri']); $filename = File::filename($scoped, "email", $mimetype); @@ -273,77 +273,58 @@ class MediaFile /** * Attempt to identify the content type of a given file. * - * @param mixed $f file handle resource, or filesystem path as string + * @param string $filepath filesystem path as string (file must exist) * @param string $originalFilename (optional) for extension-based detection * @return string * - * @fixme is this an internal or public method? It's called from GetFileAction * @fixme this seems to tie a front-end error message in, kinda confusing - * @fixme this looks like it could return a PEAR_Error in some cases, if - * type can't be identified and $config['attachments']['supported'] is true * * @throws ClientException if type is known, but not supported for local uploads */ - static function getUploadedFileType($f, $originalFilename=false) { - global $_PEAR; - - require_once 'MIME/Type.php'; - require_once 'MIME/Type/Extension.php'; - - // We have to disable auto handling of PEAR errors - $_PEAR->staticPushErrorHandling(PEAR_ERROR_RETURN); - $mte = new MIME_Type_Extension(); - - $cmd = &$_PEAR->getStaticProperty('MIME_Type', 'fileCmd'); - $cmd = common_config('attachments', 'filecommand'); - - $filetype = null; - - // If we couldn't get a clear type from the file extension, - // we'll go ahead and try checking the content. Content checks - // are unambiguous for most image files, but nearly useless - // for office document formats. - + static function getUploadedMimeType($filepath, $originalFilename=false) { // We only accept filenames to existing files - $filetype = MIME_Type::autoDetect($f); + $mimelookup = new finfo(FILEINFO_MIME_TYPE); + $mimetype = $mimelookup->file($filepath); - // The content-based sources for MIME_Type::autoDetect() - // are wildly unreliable for office-type documents. If we've - // gotten an unclear reponse back or just couldn't identify it, - // we'll try detecting a type from its extension... + // Unclear types are such that we can't really tell by the auto + // detect what they are (.bin, .exe etc. are just "octet-stream") $unclearTypes = array('application/octet-stream', 'application/vnd.ms-office', 'application/zip', // TODO: for XML we could do better content-based sniffing too 'text/xml'); - if ($originalFilename && (!$filetype || in_array($filetype, $unclearTypes))) { - $type = $mte->getMIMEType($originalFilename); - if (is_string($type)) { - $filetype = $type; + $supported = common_config('attachments', 'supported'); + + // If we didn't match, or it is an unclear match + if ($originalFilename && (!$mimetype || in_array($mimetype, $unclearTypes))) { + $type = common_supported_ext_to_mime($originalFilename); + if (!empty($type)) { + return $type; } } - $supported = common_config('attachments', 'supported'); - if ($supported === true || in_array($filetype, $supported)) { - // Restore PEAR error handlers for our DB code... - $_PEAR->staticPopErrorHandling(); - return $filetype; + // If $config['attachments']['supported'] equals boolean true, accept any mimetype + if ($supported === true || array_key_exists($mimetype, $supported)) { + // FIXME: Don't know if it always has a mimetype here because + // finfo->file CAN return false on error: http://php.net/finfo_file + // so if $supported === true, this may return something unexpected. + return $mimetype; } - $media = MIME_Type::getMedia($filetype); + + // We can conclude that we have failed to get the MIME type + $media = common_get_mime_media($mimetype); if ('application' !== $media) { // TRANS: Client exception thrown trying to upload a forbidden MIME type. // TRANS: %1$s is the file type that was denied, %2$s is the application part of // TRANS: the MIME type that was denied. $hint = sprintf(_('"%1$s" is not a supported file type on this server. ' . - 'Try using another %2$s format.'), $filetype, $media); + 'Try using another %2$s format.'), $mimetype, $media); } else { // TRANS: Client exception thrown trying to upload a forbidden MIME type. // TRANS: %s is the file type that was denied. - $hint = sprintf(_('"%s" is not a supported file type on this server.'), $filetype); + $hint = sprintf(_('"%s" is not a supported file type on this server.'), $mimetype); } - // Restore PEAR error handlers for our DB code... - $_PEAR->staticPopErrorHandling(); throw new ClientException($hint); } } diff --git a/lib/util.php b/lib/util.php index 9f7eac90f9..5f9c69dde2 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1797,6 +1797,31 @@ function common_accept_to_prefs($accept, $def = '*/*') return $prefs; } +// Match by our supported file extensions +function common_supported_ext_to_mime($fileext) +{ + // Accept a filename and take out the extension + if (strpos($fileext, '.') !== false) { + $fileext = substr(strrchr($fileext, '.'), 1); + } + + $supported = common_config('attachments', 'supported'); + foreach($supported as $type => $ext) { + if ($ext === $fileext) { + return $type; + } + } + + return false; +} + +// The MIME "media" is the part before the slash (video in video/webm) +function common_get_mime_media($type) +{ + $tmp = explode('/', $type); + return strtolower($tmp[0]); +} + function common_mime_type_match($type, $avail) { if(array_key_exists($type, $avail)) { diff --git a/tests/MediaFileTest.php b/tests/MediaFileTest.php index 6d96497c5b..d28b22e30e 100644 --- a/tests/MediaFileTest.php +++ b/tests/MediaFileTest.php @@ -29,13 +29,13 @@ class MediaFileTest extends PHPUnit_Framework_TestCase * @dataProvider fileTypeCases * */ - public function testFileType($filename, $expectedType) + public function testMimeType($filename, $expectedType) { if (!file_exists($filename)) { throw new Exception("WTF? $filename test file missing"); } - $type = MediaFile::getUploadedFileType($filename, basename($filename)); + $type = MediaFile::getUploadedMimeType($filename, basename($filename)); $this->assertEquals($expectedType, $type); } @@ -43,7 +43,7 @@ class MediaFileTest extends PHPUnit_Framework_TestCase * @dataProvider fileTypeCases * */ - public function testUploadedFileType($filename, $expectedType) + public function testUploadedMimeType($filename, $expectedType) { if (!file_exists($filename)) { throw new Exception("WTF? $filename test file missing"); @@ -52,7 +52,7 @@ class MediaFileTest extends PHPUnit_Framework_TestCase fwrite($tmp, file_get_contents($filename)); $tmp_metadata = stream_get_meta_data($tmp); - $type = MediaFile::getUploadedFileType($tmp_metadata['uri'], basename($filename)); + $type = MediaFile::getUploadedMimeType($tmp_metadata['uri'], basename($filename)); $this->assertEquals($expectedType, $type); }