Bugfix handling attachments MIME type / content-type as it was broken.

This commit is contained in:
the-djmaze
2022-11-16 15:14:00 +01:00
parent ee5fb1a83e
commit f4448635d1
12 changed files with 461 additions and 302 deletions

View File

@@ -22,7 +22,8 @@ const
ics: 'text/calendar',
xml: 'text/xml',
json: app+'json',
asc: app+'pgp-signature',
// asc: app+'pgp-signature',
// asc: app+'pgp-keys',
p10: app+'pkcs10',
p7c: app+'pkcs7-mime',
p7m: app+'pkcs7-mime',

View File

@@ -27,6 +27,7 @@ export class ComposeAttachmentModel extends AbstractModel {
fileName: fileName,
size: size,
tempName: '',
type: '', // application/octet-stream
progress: 0,
error: '',
@@ -54,7 +55,7 @@ export class ComposeAttachmentModel extends AbstractModel {
return null === localSize ? '' : FileInfo.friendlySize(localSize);
},
mimeType: () => FileInfo.getContentType(this.fileName()),
mimeType: () => this.type() || FileInfo.getContentType(this.fileName()),
fileExt: () => FileInfo.getExtension(this.fileName()),
iconClass: () => FileInfo.getIconClass(this.fileExt(), this.mimeType())

View File

@@ -11,7 +11,7 @@ import {
SetSystemFoldersNotification
} from 'Common/EnumsUser';
import { pInt, isArray, arrayLength, forEachObjectEntry } from 'Common/Utils';
import { pInt, isArray, arrayLength } from 'Common/Utils';
import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html';
import { koArrayWithDestroy, addObservablesTo, addComputablesTo, addSubscribablesTo } from 'External/ko';
@@ -973,28 +973,22 @@ export class ComposePopupView extends AbstractViewPopup {
if (arrayLength(downloads)) {
Remote.request('MessageUploadAttachments',
(iError, oData) => {
if (!iError) {
forEachObjectEntry(oData.Result, (tempName, id) => {
const attachment = this.getAttachmentById(id);
if (attachment) {
attachment.tempName(tempName);
attachment
.waiting(false)
.uploading(false)
.complete(true);
const result = oData?.Result;
downloads.forEach((id, index) => {
const attachment = this.getAttachmentById(id);
if (attachment) {
attachment
.waiting(false)
.uploading(false)
.complete(true);
if (iError || !result?.[index]) {
attachment.error(getUploadErrorDescByCode(UploadErrorCode.NoFileUploaded));
} else {
attachment.tempName(result[index].TempName);
attachment.type(result[index].MimeType);
}
});
} else {
this.attachments.forEach(attachment => {
if (attachment?.fromMessage) {
attachment
.waiting(false)
.uploading(false)
.complete(true)
.error(getUploadErrorDescByCode(UploadErrorCode.NoFileUploaded));
}
});
}
}
});
},
{
Attachments: downloads
@@ -1123,6 +1117,7 @@ export class ComposePopupView extends AbstractViewPopup {
attachment.size(attachmentJson.Size ? pInt(attachmentJson.Size) : 0);
attachment.tempName(attachmentJson.TempName ? attachmentJson.TempName : '');
attachment.isInline = false;
attachment.type(attachmentJson.MimeType);
}
}
});
@@ -1178,20 +1173,6 @@ export class ComposePopupView extends AbstractViewPopup {
return this.attachments.find(item => item && id === item.id);
}
/**
* @returns {Object}
*/
prepareAttachmentsForSendOrSave() {
const result = {};
this.attachments.forEach(item => {
if (item?.complete() && item?.tempName() && item?.enabled()) {
result[item.tempName()] = [item.fileName(), item.isInline ? '1' : '0', item.CID, item.contentLocation];
}
});
return result;
}
/**
* @param {MessageModel} message
*/
@@ -1237,6 +1218,7 @@ export class ComposePopupView extends AbstractViewPopup {
if (message) {
let reply = [ComposeType.Reply, ComposeType.ReplyAll].includes(type);
if (reply || [ComposeType.Forward, ComposeType.Draft, ComposeType.EditAsNew].includes(type)) {
// item instanceof AttachmentModel
message.attachments.forEach(item => {
if (!reply || item.isLinked()) {
const attachment = new ComposeAttachmentModel(
@@ -1249,6 +1231,7 @@ export class ComposePopupView extends AbstractViewPopup {
item.contentLocation
);
attachment.fromMessage = true;
attachment.type(item.mimeType);
this.addAttachment(attachment);
}
});
@@ -1405,6 +1388,20 @@ export class ComposePopupView extends AbstractViewPopup {
async getMessageRequestParams(sSaveFolder, draft)
{
// Prepare ComposeAttachmentModel attachments
const attachments = {};
this.attachments.forEach(item => {
if (item?.complete() && item?.tempName() && item?.enabled()) {
attachments[item.tempName()] = {
name: item.fileName(),
inline: item.isInline,
cid: item.CID,
location: item.contentLocation,
type: item.mimeType()
};
}
});
const
identity = this.currentIdentity(),
params = {
@@ -1422,7 +1419,7 @@ export class ComposePopupView extends AbstractViewPopup {
InReplyTo: this.sInReplyTo,
References: this.sReferences,
MarkAsImportant: this.markAsImportant() ? 1 : 0,
Attachments: this.prepareAttachmentsForSendOrSave(),
Attachments: attachments,
// Only used at send, not at save:
Dsn: this.requestDsn() ? 1 : 0,
ReadReceiptRequest: this.requestReadReceipt() ? 1 : 0

View File

@@ -69,8 +69,8 @@ export class OpenPgpImportPopupView extends AbstractViewPopup {
this.close();
}
onShow() {
this.key('');
onShow(key) {
this.key(key || '');
this.keyError(false);
this.keyErrorMessage('');
}

View File

@@ -55,10 +55,17 @@ import { MimeToMessage } from 'Mime/Utils';
import { MessageModel } from 'Model/Message';
import { showScreenPopup } from 'Knoin/Knoin';
import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
import { GnuPGUserStore } from 'Stores/User/GnuPG';
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
const
oMessageScrollerDom = () => elementById('messageItem') || {},
currentMessage = MessageUserStore.message;
currentMessage = MessageUserStore.message,
fetchRaw = url => rl.fetch(url).then(response => response.ok && response.text());
export class MailMessageView extends AbstractViewRight {
constructor() {
@@ -284,22 +291,23 @@ export class MailMessageView extends AbstractViewRight {
el = eqs(event, '.attachmentsPlace .attachmentName');
if (el) {
const attachment = ko.dataFor(el);
if (attachment?.linkDownload()) {
if ('message/rfc822' == attachment.mimeType) {
const attachment = ko.dataFor(el), url = attachment?.linkDownload();
if (url) {
if ('application/pgp-keys' == attachment.mimeType
&& (OpenPGPUserStore.isSupported() || GnuPGUserStore.isSupported())) {
fetchRaw(url).then(text =>
showScreenPopup(OpenPgpImportPopupView, [text])
);
} else if ('message/rfc822' == attachment.mimeType) {
// TODO
rl.fetch(attachment.linkDownload()).then(response => {
if (response.ok) {
response.text().then(text => {
const oMessage = new MessageModel();
MimeToMessage(text, oMessage);
// cleanHTML
oMessage.viewPopupMessage();
});
}
fetchRaw(url).then(text => {
const oMessage = new MessageModel();
MimeToMessage(text, oMessage);
// cleanHTML
oMessage.viewPopupMessage();
});
} else {
download(attachment.linkDownload(), attachment.fileName);
download(url, attachment.fileName);
}
}
}

View File

@@ -501,180 +501,6 @@ abstract class Utils
return false === $iLast ? '' : \strtolower(\substr($sFileName, $iLast + 1));
}
public static function MimeContentType(string $sFileName) : string
{
$sResult = 'application/octet-stream';
$sFileName = \trim(\strtolower($sFileName));
if ('winmail.dat' === $sFileName)
{
return 'application/ms-tnef';
}
$aMimeTypes = array(
'eml' => 'message/rfc822',
'mime' => 'message/rfc822',
'txt' => 'text/plain',
'text' => 'text/plain',
'def' => 'text/plain',
'list' => 'text/plain',
'in' => 'text/plain',
'ini' => 'text/plain',
'log' => 'text/plain',
'sql' => 'text/plain',
'cfg' => 'text/plain',
'conf' => 'text/plain',
'rtx' => 'text/richtext',
'vcard' => 'text/vcard',
'vcf' => 'text/vcard',
'htm' => 'text/html',
'html' => 'text/html',
'csv' => 'text/csv',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'xml' => 'text/xml',
'json' => 'application/json',
'swf' => 'application/x-shockwave-flash',
'hlp' => 'application/winhlp',
'wgt' => 'application/widget',
'chm' => 'application/vnd.ms-htmlhelp',
'asc' => 'application/pgp-signature',
'p10' => 'application/pkcs10',
'p7c' => 'application/pkcs7-mime',
'p7m' => 'application/pkcs7-mime',
'p7s' => 'application/pkcs7-signature',
'torrent' => 'application/x-bittorrent',
// scripts
'js' => 'application/javascript',
'pl' => 'text/perl',
'css' => 'text/css',
'asp' => 'text/asp',
'php' => 'application/x-php',
// images
'png' => 'image/png',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'jpe' => 'image/jpeg',
'jfif' => 'image/jpeg',
'gif' => 'image/gif',
'bmp' => 'image/bmp',
'cgm' => 'image/cgm',
'ief' => 'image/ief',
'ico' => 'image/x-icon',
'tif' => 'image/tiff',
'tiff' => 'image/tiff',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
'djv' => 'image/vnd.djvu',
'djvu' => 'image/vnd.djvu',
'webp' => 'image/webp',
// archives
'zip' => 'application/zip',
'7z' => 'application/x-7z-compressed',
'rar' => 'application/x-rar-compressed',
'exe' => 'application/x-msdownload',
'dll' => 'application/x-msdownload',
'scr' => 'application/x-msdownload',
'com' => 'application/x-msdownload',
'bat' => 'application/x-msdownload',
'msi' => 'application/x-msdownload',
'cab' => 'application/vnd.ms-cab-compressed',
'gz' => 'application/x-gzip',
'tgz' => 'application/x-gzip',
'bz' => 'application/x-bzip',
'bz2' => 'application/x-bzip2',
'deb' => 'application/x-debian-package',
'tar' => 'application/x-tar',
// fonts
'psf' => 'application/x-font-linux-psf',
'otf' => 'application/x-font-otf',
'pcf' => 'application/x-font-pcf',
'snf' => 'application/x-font-snf',
'ttf' => 'application/x-font-ttf',
'ttc' => 'application/x-font-ttf',
// audio
'aac' => 'audio/aac',
'flac' => 'audio/flac',
'mp3' => 'audio/mpeg',
'aif' => 'audio/aiff',
'aifc' => 'audio/aiff',
'aiff' => 'audio/aiff',
'wav' => 'audio/x-wav',
'midi' => 'audio/midi',
'mp4a' => 'audio/mp4',
'ogg' => 'audio/ogg',
'weba' => 'audio/webm',
'm3u' => 'audio/x-mpegurl',
// video
'qt' => 'video/quicktime',
'mov' => 'video/quicktime',
'avi' => 'video/x-msvideo',
'mpg' => 'video/mpeg',
'mpeg' => 'video/mpeg',
'mpe' => 'video/mpeg',
'm1v' => 'video/mpeg',
'm2v' => 'video/mpeg',
'3gp' => 'video/3gpp',
'3g2' => 'video/3gpp2',
'h261' => 'video/h261',
'h263' => 'video/h263',
'h264' => 'video/h264',
'jpgv' => 'video/jpgv',
'mp4' => 'video/mp4',
'mp4v' => 'video/mp4',
'mpg4' => 'video/mp4',
'ogv' => 'video/ogg',
'webm' => 'video/webm',
'm4v' => 'video/x-m4v',
'asf' => 'video/x-ms-asf',
'asx' => 'video/x-ms-asf',
'wm' => 'video/x-ms-wm',
'wmv' => 'video/x-ms-wmv',
'wmx' => 'video/x-ms-wmx',
'wvx' => 'video/x-ms-wvx',
'movie' => 'video/x-sgi-movie',
// adobe
'pdf' => 'application/pdf',
'psd' => 'image/vnd.adobe.photoshop',
'ai' => 'application/postscript',
'eps' => 'application/postscript',
'ps' => 'application/postscript',
// ms office
'doc' => 'application/msword',
'dot' => 'application/msword',
'rtf' => 'application/rtf',
'xls' => 'application/vnd.ms-excel',
'ppt' => 'application/vnd.ms-powerpoint',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
// open office
'odt' => 'application/vnd.oasis.opendocument.text',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odp' => 'application/vnd.oasis.opendocument.presentation'
);
$sExt = static::GetFileExtension($sFileName);
if (\strlen($sExt) && isset($aMimeTypes[$sExt]))
{
$sResult = $aMimeTypes[$sExt];
}
return $sResult;
}
public static function ContentTypeType(string $sContentType, string $sFileName) : string
{
$sContentType = \strtolower($sContentType);
@@ -695,6 +521,7 @@ abstract class Utils
case 'application/x-bzip2':
case 'application/x-debian-package':
case 'application/x-tar':
case 'application/gtar':
return 'archive';
case 'application/msword':

View File

@@ -22,46 +22,28 @@ class Attachment
*/
private $rResource;
/**
* @var string
*/
private $sFileName;
private string $sFileName;
/**
* @var int
*/
private $iFileSize;
private int $iFileSize;
/**
* @var string
*/
private $sCID;
private string $sCID;
/**
* @var bool
*/
private $bIsInline;
private bool $bIsInline;
/**
* @var bool
*/
private $bIsLinked;
private bool $bIsLinked;
/**
* @var array
*/
private $aCustomContentTypeParams;
private array $aCustomContentTypeParams;
/**
* @var string
*/
private $sContentLocation;
private string $sContentLocation;
private string $sContentType;
/**
* @param resource $rResource
*/
function __construct($rResource, string $sFileName, int $iFileSize, bool $bIsInline,
bool $bIsLinked, string $sCID, array $aCustomContentTypeParams = [], string $sContentLocation = '')
bool $bIsLinked, string $sCID, array $aCustomContentTypeParams = [],
string $sContentLocation = '', string $sContentType = '')
{
$this->rResource = $rResource;
$this->sFileName = $sFileName;
@@ -71,6 +53,10 @@ class Attachment
$this->sCID = $sCID;
$this->aCustomContentTypeParams = $aCustomContentTypeParams;
$this->sContentLocation = $sContentLocation;
$this->sContentType = $sContentType
?: \SnappyMail\File\MimeType::fromStream($rResource, $sFileName)
?: \SnappyMail\File\MimeType::fromFilename($sFileName)
?: 'application/octet-stream';
}
/**
@@ -83,7 +69,7 @@ class Attachment
public function ContentType() : string
{
return \MailSo\Base\Utils::MimeContentType($this->sFileName);
return $this->sContentType;
}
public function CustomContentTypeParams() : array

View File

@@ -997,18 +997,23 @@ class Actions
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name']);
// Detect content-type
$type = \SnappyMail\File\MimeType::fromFile($aFile['tmp_name'], $aFile['name'])
?: \SnappyMail\File\MimeType::fromFilename($aFile['name']);
if ($type) {
$aFile['type'] = $type;
$sSavedName .= \SnappyMail\File\MimeType::toExtension($type);
}
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
$iError = Enumerations\UploadError::ON_SAVING;
} else {
$sUploadName = $aFile['name'];
$iSize = $aFile['size'];
$sMimeType = $aFile['type'];
$aResponse['Attachment'] = array(
'Name' => $sUploadName,
'Name' => $aFile['name'],
'TempName' => $sSavedName,
'MimeType' => $sMimeType,
'Size' => (int)$iSize
'MimeType' => $aFile['type'],
'Size' => (int) $aFile['size']
);
}
}
@@ -1041,9 +1046,12 @@ class Actions
$iError = $this->GetActionParam('Error', Enumerations\UploadError::UNKNOWN);
if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) {
$sMimeType = \strtolower(\MailSo\Base\Utils::MimeContentType($aFile['name']));
if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg'))) {
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name']);
$sMimeType = \SnappyMail\File\MimeType::fromFile($aFile['tmp_name'], $aFile['name'])
?: \SnappyMail\File\MimeType::fromFilename($aFile['name'])
?: $aFile['type'];
if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg', 'image/webp'))) {
$sSavedName = 'upload-post-' . \md5($aFile['name'] . $aFile['tmp_name'])
. \SnappyMail\File\MimeType::toExtension($sContentType);
if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) {
$iError = Enumerations\UploadError::ON_SAVING;
} else {

View File

@@ -603,37 +603,53 @@ trait Messages
try
{
$aAttachments = $this->GetActionParam('Attachments', array());
if (\is_array($aAttachments) && \count($aAttachments))
{
$mResult = array();
foreach ($aAttachments as $sAttachment)
{
if ($aValues = \RainLoop\Utils::DecodeKeyValuesQ($sAttachment))
{
if (!\is_array($aAttachments)) {
$aAttachments = [];
}
if (\count($aAttachments)) {
$oFilesProvider = $this->FilesProvider();
foreach ($aAttachments as $mIndex => $sAttachment) {
$aAttachments[$mIndex] = false;
if ($aValues = \RainLoop\Utils::DecodeKeyValuesQ($sAttachment)) {
$sFolder = isset($aValues['Folder']) ? (string) $aValues['Folder'] : '';
$iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
$sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
$sTempName = \md5($sAttachment);
if (!$this->FilesProvider()->FileExists($oAccount, $sTempName))
{
$sTempName = \sha1($sAttachment);
if (!$oFilesProvider->FileExists($oAccount, $sTempName)) {
$this->MailClient()->MessageMimeStream(
function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($oAccount, &$mResult, $sTempName, $sAttachment, $self) {
if (is_resource($rResource))
{
$sContentType = (empty($sFileName)) ? 'text/plain' : \MailSo\Base\Utils::MimeContentType($sFileName);
$sFileName = $self->MainClearFileName($sFileName, $sContentType, $sMimeIndex);
function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($oAccount, $sTempName, $self, &$aAttachments, $mIndex) {
if (\is_resource($rResource)) {
$sContentType =
$sContentType
?: \SnappyMail\File\MimeType::fromStream($rResource, $sFileName)
?: \SnappyMail\File\MimeType::fromFilename($sFileName)
?: 'application/octet-stream'; // 'text/plain'
if ($self->FilesProvider()->PutFile($oAccount, $sTempName, $rResource))
{
$mResult[$sTempName] = $sAttachment;
// $sFileName = $self->MainClearFileName($sFileName, $sContentType, $sMimeIndex);
$sTempName .= \SnappyMail\File\MimeType::toExtension($sContentType);
if ($self->FilesProvider()->PutFile($oAccount, $sTempName, $rResource)) {
$aAttachments[$mIndex] = [
// 'Name' => $sFileName,
'TempName' => $sTempName,
'MimeType' => $sContentType
// 'Size' => 0
];
}
}
}, $sFolder, $iUid, $sMimeIndex);
}
else
{
$mResult[$sTempName] = $sAttachment;
} else {
$rResource = $oFilesProvider->GetFile($oAccount, $sTempName);
$sContentType = \SnappyMail\File\MimeType::fromStream($rResource, $sTempName)
?: \SnappyMail\File\MimeType::fromFilename($sTempName)
?: 'application/octet-stream'; // 'text/plain'
$aAttachments[$mIndex] = [
// 'Name' => $sFileName,
'TempName' => $sTempName,
'MimeType' => $sContentType
// 'Size' => $oFilesProvider->FileSize($oAccount, $sTempName)
];
}
}
}
@@ -644,7 +660,7 @@ trait Messages
throw new ClientException(Notifications::MailServerError, $oException);
}
return $this->DefaultResponse(__FUNCTION__, $mResult);
return $this->DefaultResponse(__FUNCTION__, $aAttachments);
}
/**
@@ -1231,10 +1247,11 @@ trait Messages
{
foreach ($aAttachments as $sTempName => $aData)
{
$sFileName = (string) $aData[0];
$bIsInline = (bool) $aData[1];
$sCID = (string) $aData[2];
$sContentLocation = isset($aData[3]) ? (string) $aData[3] : '';
$sFileName = (string) $aData['name'];
$bIsInline = (bool) $aData['inline'];
$sCID = (string) $aData['cid'];
$sContentLocation = (string) $aData['location'];
$sMimeType = (string) $aData['type'];
$rResource = $this->FilesProvider()->GetFile($oAccount, $sTempName);
if (\is_resource($rResource))
@@ -1244,7 +1261,7 @@ trait Messages
$oMessage->Attachments()->append(
new \MailSo\Mime\Attachment($rResource, $sFileName, $iFileSize, $bIsInline,
\in_array(trim(trim($sCID), '<>'), $aFoundCids),
$sCID, array(), $sContentLocation
$sCID, array(), $sContentLocation, $sMimeType
)
);
}

View File

@@ -149,7 +149,9 @@ trait Raw
if ('.pdf' === \substr($sFileNameIn,-4)) {
$sContentTypeOut = 'application/pdf'; // application/octet-stream
} else {
$sContentTypeOut = $sContentTypeIn ?: \MailSo\Base\Utils::MimeContentType($sFileNameIn);
$sContentTypeOut = $sContentTypeIn
?: \SnappyMail\File\MimeType::fromFilename($sFileNameIn)
?: 'application/octet-stream';
}
$sFileNameOut = $this->MainClearFileName($sFileNameIn, $sContentTypeIn, $sMimeIndex);
@@ -197,10 +199,12 @@ trait Raw
if ('.pdf' === \substr($sFileName, -4)) {
// https://github.com/the-djmaze/snappymail/issues/144
$sContentType = 'application/pdf';
} else if ($sContentTypeIn) {
$sContentType = $sContentTypeIn;
} else if (!$sContentType) {
$sContentType = $sFileName ? \MailSo\Base\Utils::MimeContentType($sFileName) : 'text/plain';
} else {
$sContentType = $sContentTypeIn
?: $sContentType
// ?: \SnappyMail\File\MimeType::fromStream($rResource, $sFileName)
?: \SnappyMail\File\MimeType::fromFilename($sFileName)
?: 'application/octet-stream';
}
if (!$bDownload)

View File

@@ -0,0 +1,34 @@
<?php
$magic = array(
'#^\x37\x7A.*#s' => 'application/x-7z-compressed', # unknown by magic.mime
'#^BZh.*#s' => 'application/x-bzip2',
'#^\x1f\x8b.*#s' => 'application/x-gzip',
'#^.{257}ustar \x00.*#s' => 'application/x-gtar',
'#^.{257}ustar\x00.*#s' => 'application/x-tar',
'#^Rar!.*#s' => 'application/x-rar-compressed',
'#^PK\x03\x04.*#s' => 'application/zip',
'#^%PDF-.*#s' => 'application/pdf',
'#^[CF]WS.*#s' => 'application/x-shockwave-flash',
'#^(.*\n)?(INSERT|CREATE|DROP|DELETE|ALTER|UPDATE)\ .*#is' => 'text/x-sql',
'#^BEGIN:VCARD#s' => 'text/x-vcard',
'#^GIF8.*#s' => 'image/gif',
'#^.{8}heic#s' => 'image/heic', // https://nokiatech.github.io/heif/technical.html
'#^\xFF\xD8.*#s' => 'image/jpeg',
'#^\x89PNG.*#s' => 'image/png',
'#^.PNG.*#s' => 'image/png',
'#^<svg.*#s' => 'image/svg+xml', # wild guess, unknown by magic.mime
'#^RIFF.{4}WEBP#s' => 'image/webp',
'#^FLV.*#s' => 'video/x-flv',
'#^OggS.+\x80theora.*#s' => 'video/ogg',
'#^ID3.*#s' => 'audio/mpeg', # wild guess?
'#^\xff\xfa.*#s' => 'audio/mpeg', # wild guess?
'#^OggS.+\x01vorbis.*#s' => 'audio/ogg',
'#^\xD0\xCF\x11\xE0\xA1.*#s' => 'application/msword',
'#^OggS.*#s' => 'application/ogg',
);

View File

@@ -0,0 +1,276 @@
<?php
namespace SnappyMail\File;
// use MailSo\Mime\Enumerations\MimeType;
abstract class MimeType
{
protected static $finfo = null;
protected static function initFInfo() : void
{
if (null === self::$finfo) {
// if (!\getenv('MAGIC')) { \putenv('MAGIC='.__DIR__.'/magic.mime'.(WINDOWS_OS?'.win32':'')); } # /usr/share/misc/magic
self::$finfo = \class_exists('finfo', false) ? new \finfo(FILEINFO_MIME) : false; // FILEINFO_CONTINUE
}
}
protected static function detectDeeper(string $mime, string $name = '') : string
{
if ('application/ogg' === $mime
|| 'application/vnd.ms-office' === $mime
|| 'application/octet-stream' === $mime
|| 'application/zip' === $mime) {
return static::fromFilename($name) ?: $mime;
}
return $mime;
}
public static function fromFile(string $filename, string $name = '') : ?string
{
$mime = null;
if (\is_file($filename)) {
static::initFInfo();
if (self::$finfo) {
$mime = \preg_replace('#[,;].*#', '', self::$finfo->file($filename));
}
if (!$mime && $fp = \fopen($filename, 'rb')) {
$mime = self::fromStream($fp);
\fclose($fp);
}
if ('application/zip' === \str_replace('/x-', '/', $mime)) {
$zip = new \ZipArchive($filename);
if (false !== $zip->locateName('word/_rels/document.xml.rels')) {
return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
if (false !== $zip->locateName('xl/_rels/workbook.xml.rels')) {
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
}
}
}
return $mime ? static::detectDeeper($mime, $name ?: $filename) : null;
}
public static function fromStream($stream, string $name = '') : ?string
{
if (\is_resource($stream) && \stream_get_meta_data($stream)['seekable']) {
$pos = \ftell($stream);
// if (\is_int($pos) && \rewind($stream)) {
if (\is_int($pos) && 0 === \fseek($stream, 0)) {
// $str = \fread($stream, 265);
$str = \stream_get_contents($stream, 265, 0);
\fseek($stream, $pos);
if ($str) {
return static::fromString($str, $name);
}
}
}
return null;
}
public static function fromString(string &$str, string $name = '') : ?string
{
static::initFInfo();
$mime = self::$finfo
? \preg_replace('#[,;].*#', '', self::$finfo->buffer($str))
: self::getFromData($str);
return $mime ? static::detectDeeper($mime, $name) : null;
}
protected static function getFromData(string $str) : ?string
{
if (\str_contains($str, '-----BEGIN PGP SIGNATURE-----')) {
return 'application/pgp-signature';
}
if (\preg_match('/-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----/', $str)) {
return 'application/pgp-keys';
}
static $magic;
if (!$magic) {
require __DIR__ . '/magic.mime.php';
}
$str = \preg_replace(\array_keys($magic), \array_values($magic), $str, 1, $c);
return $c ? $str : null;
}
public static function fromFilename(string $filename) : ?string
{
$filename = \strtolower($filename);
if ('winmail.dat' === $filename) {
return 'application/ms-tnef';
}
$extension = \explode('.', $filename);
$extension = \array_pop($extension);
return isset(static::$types[$extension]) ? static::$types[$extension] : null;
}
/**
* Issue with 'text/plain'
*/
public static function toExtension(string $mime, bool $include_dot = true) : ?string
{
$mime = \strtolower($mime);
if ('application/pgp-signature' == $mime || 'application/pgp-keys' == $mime) {
$ext = 'asc';
} else {
$mime = \str_replace('application/x-tar', 'application/gtar', $mime);
$ext = \array_search($mime, static::$types)
?: \array_search(\str_replace('/x-', '/', $mime), static::$types)
?: \array_search(\str_replace('/', '/x-', $mime), static::$types)
?: 'bin';
}
return ($include_dot ? '.' : '') . $ext;
}
protected static $types = [
'7z' => 'application/x-7z-compressed',
'ai' => 'application/postscript',
// 'asc' => 'application/pgp-signature',
// 'asc' => 'application/pgp-keys',
'bat' => 'application/x-msdownload',
'bz' => 'application/x-bzip',
'bz2' => 'application/x-bzip2',
'cab' => 'application/vnd.ms-cab-compressed',
'chm' => 'application/vnd.ms-htmlhelp',
'com' => 'application/x-msdownload',
'deb' => 'application/x-debian-package',
'dll' => 'application/x-msdownload',
'doc' => 'application/msword',
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'dot' => 'application/msword',
'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'eps' => 'application/postscript',
'epub' => 'application/epub',
'exe' => 'application/x-msdownload',
'gz' => 'application/gzip',
'gz' => 'application/x-gzip',
'hlp' => 'application/winhlp',
'js' => 'application/javascript',
'json' => 'application/json',
'msi' => 'application/x-msdownload',
'odp' => 'application/vnd.oasis.opendocument.presentation',
'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
'odt' => 'application/vnd.oasis.opendocument.text',
'ogx' => 'application/ogg',
'p10' => 'application/pkcs10',
'p7c' => 'application/pkcs7-mime',
'p7m' => 'application/pkcs7-mime',
'p7s' => 'application/pkcs7-signature',
'pdf' => 'application/pdf',
'php' => 'application/x-httpd-php',
'php' => 'application/x-php',
'ppt' => 'application/vnd.ms-powerpoint',
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'ps' => 'application/postscript',
'psd' => 'image/vnd.adobe.photoshop',
'rar' => 'application/rar-compressed',
'rar' => 'application/x-rar-compressed',
'rtf' => 'application/rtf',
'scr' => 'application/x-msdownload',
'sql' => 'application/sql',
'swf' => 'application/shockwave-flash',
'swf' => 'application/x-shockwave-flash',
'tar' => 'application/gtar',
// 'tar' => 'application/x-tar',
// 'tgz' => 'application/x-gzip',
'torrent' => 'application/x-bittorrent',
'wgt' => 'application/widget',
'xls' => 'application/vnd.ms-excel',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'zip' => 'application/zip',
'aac' => 'audio/aac',
'aif' => 'audio/aiff',
'aifc' => 'audio/aiff',
'aiff' => 'audio/aiff',
'flac' => 'audio/flac',
'm3u' => 'audio/x-mpegurl',
'midi' => 'audio/midi',
'mp3' => 'audio/mpeg',
'mp4a' => 'audio/mp4',
'ogg' => 'audio/ogg',
'wav' => 'audio/wav',
'weba' => 'audio/webm',
'ttf' => 'font/ttf',
'woff' => 'font/woff',
'woff2' => 'font/woff2',
'bmp' => 'image/bmp',
'cgm' => 'image/cgm',
'djv' => 'image/vnd.djvu',
'djvu' => 'image/vnd.djvu',
'gif' => 'image/gif',
// 'heic' => 'image/heic',
'ico' => 'image/vnd.microsoft.icon',
// 'ico' => 'image/x-icon',
'ief' => 'image/ief',
'jpeg' => 'image/jpeg',
'jfif' => 'image/jpeg',
'jpe' => 'image/jpeg',
'jpg' => 'image/jpeg',
'png' => 'image/png',
'svg' => 'image/svg+xml',
'svgz' => 'image/svg+xml',
'tiff' => 'image/tiff',
'tif' => 'image/tiff',
'webp' => 'image/webp',
'eml' => 'message/rfc822',
'mime' => 'message/rfc822',
'txt' => 'text/plain',
'asp' => 'text/asp',
'cfg' => 'text/plain',
'conf' => 'text/plain',
'css' => 'text/css',
'csv' => 'text/csv',
'def' => 'text/plain',
'html' => 'text/html',
'htm' => 'text/html',
'ics' => 'text/calendar',
'ifb' => 'text/calendar',
'in' => 'text/plain',
'ini' => 'text/plain',
'list' => 'text/plain',
'log' => 'text/plain',
'pl' => 'text/perl',
'rtx' => 'text/richtext',
'text' => 'text/plain',
'vcf' => 'text/vcard',
'vcard' => 'text/vcard',
'xml' => 'text/xml',
'3g2' => 'video/3gpp2',
'3gp' => 'video/3gpp',
'asf' => 'video/x-ms-asf',
'asx' => 'video/x-ms-asf',
'avi' => 'video/x-msvideo',
'flv' => 'video/flv',
'h261' => 'video/h261',
'h263' => 'video/h263',
'h264' => 'video/h264',
'jpgv' => 'video/jpgv',
'm4v' => 'video/x-m4v',
'mov' => 'video/quicktime',
'movie' => 'video/x-sgi-movie',
'mp4' => 'video/mp4',
'mp4v' => 'video/mp4',
'mpeg' => 'video/mpeg',
'm1v' => 'video/mpeg',
'm2v' => 'video/mpeg',
'mpe' => 'video/mpeg',
'mpg' => 'video/mpeg',
'mpg4' => 'video/mp4',
'ogv' => 'video/ogg',
'qt' => 'video/quicktime',
'webm' => 'video/webm',
'wm' => 'video/x-ms-wm',
'wmv' => 'video/x-ms-wmv',
'wmx' => 'video/x-ms-wmx',
'wvx' => 'video/x-ms-wvx',
];
}