Laravel: Form file uploads failing - incorrect type detected
The problem was that PHP uses the file
command from the OS (in this case Ubuntu) to guess the extension based on the contents of the file. It was getting it wrong. Upgrading Ubuntu would probably help.
docx
is Microsoft Word Open XML Format Document, a XML-based and all the content is stored as separate files, and ultimately compacted in a single, ZIP-compressed file. It seems like a file container.
And we should not take file extension as same with mimetype. As far as know a normal ( A mp4 file with docx should not take as normal ) file with docx
file extension can has one of these mimetypes.
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
'application/zip'
'application/CDFV2'
In your code,
$file->getClientOriginalExtension()
extract extension from the original file name that was uploaded which should not be considered as a safe value.
$file->getClientMimeType()
extract the mime type from client request which should not be considered as a safe value.
Both these two functin implemented in ./vendor/symfony/http-foundation/File/UploadedFile.php
Then take a look at last function in your code,
$file->extension()
use some method to guess (which maybe not accurate) the file extension. In sequence php's finfo
(manual) which base on the file content, the system file
. As the below source code it's guess, so it's not accurate all the time. Refer to file command apparently returning wrong MIME type.
Here you should get it. If you want to go further, see below source code of the function extension
.
Source Code
extension
call guessExtension
to guess the file extension, and guessExtension
use the mimetype returned by guessMiMeType
// FileHelpers.php
public function extension()
{
return $this->guessExtension();
}
// File.php
public function guessExtension()
{
return MimeTypes::getDefault()->getExtensions($this->getMimeType())[0] ?? null;
}
...
public function getMimeType()
{
return MimeTypes::getDefault()->guessMimeType($this->getPathname());
}
guessMiMeType
use two guesser to guess the mimetype. php's finfo
and system's file
, and finfo
take high priority than file
.
//MimeTypes.php
public function __construct(array $map = [])
{
foreach ($map as $mimeType => $extensions) {
$this->extensions[$mimeType] = $extensions;
foreach ($extensions as $extension) {
$this->mimeTypes[$extension] = $mimeType;
}
}
$this->registerGuesser(new FileBinaryMimeTypeGuesser());
$this->registerGuesser(new FileinfoMimeTypeGuesser());
}
...
/**
* Registers a MIME type guesser.
*
* The last registered guesser has precedence over the other ones.
*/
public function registerGuesser(MimeTypeGuesserInterface $guesser)
{
array_unshift($this->guessers, $guesser);
}
...
public function guessMimeType(string $path): ?string
{
foreach ($this->guessers as $guesser) {
if (!$guesser->isGuesserSupported()) {
continue;
}
if (null !== $mimeType = $guesser->guessMimeType($path)) {
return $mimeType;
}
}
if (!$this->isGuesserSupported()) {
throw new LogicException('Unable to guess the MIME type as no guessers are available (have you enable the php_fileinfo extension?).');
}
return null;
}
//FileinfoMimeTypeGuesser.php
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {
throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
}
if (!$this->isGuesserSupported()) {
throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
}
if (false === $finfo = new \finfo(FILEINFO_MIME_TYPE, $this->magicFile)) {
return null;
}
return $finfo->file($path);
}
//FileBianryMimeTypeGuesser.php
public function __construct(string $cmd = 'file -b --mime %s 2>/dev/null')
{
$this->cmd = $cmd;
}
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {
throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
}
if (!$this->isGuesserSupported()) {
throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
}
ob_start();
// need to use --mime instead of -i. see #6641
passthru(sprintf($this->cmd, escapeshellarg($path)), $return);
if ($return > 0) {
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\.]+)#i', $type, $match)) {
// it's not a type, but an error message
return null;
}
return $match[1];
}
Safari is known for it's mime types, that are not correct. I'm sure in your case, it isn't sending docx mime like it should, but instead it is saying it is octet-stream
. Well, your only workaround is(if your file is not corrupted) to manually set extension of file to correct one or manually send file from browser, making sure the mime is correct.