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); }