From f4448635d1046275e80c0e259d8615a77dace3cb Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 16 Nov 2022 15:14:00 +0100 Subject: [PATCH] Bugfix handling attachments MIME type / content-type as it was broken. --- dev/Common/File.js | 3 +- dev/Model/ComposeAttachment.js | 3 +- dev/View/Popup/Compose.js | 71 +++-- dev/View/Popup/OpenPgpImport.js | 4 +- dev/View/User/MailBox/MessageView.js | 36 ++- .../0.0.0/app/libraries/MailSo/Base/Utils.php | 175 +---------- .../app/libraries/MailSo/Mime/Attachment.php | 46 +-- .../0.0.0/app/libraries/RainLoop/Actions.php | 28 +- .../libraries/RainLoop/Actions/Messages.php | 73 +++-- .../app/libraries/RainLoop/Actions/Raw.php | 14 +- .../libraries/snappymail/file/magic.mime.php | 34 +++ .../libraries/snappymail/file/mimetype.php | 276 ++++++++++++++++++ 12 files changed, 461 insertions(+), 302 deletions(-) create mode 100644 snappymail/v/0.0.0/app/libraries/snappymail/file/magic.mime.php create mode 100644 snappymail/v/0.0.0/app/libraries/snappymail/file/mimetype.php diff --git a/dev/Common/File.js b/dev/Common/File.js index 9cf850106..acd5a370e 100644 --- a/dev/Common/File.js +++ b/dev/Common/File.js @@ -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', diff --git a/dev/Model/ComposeAttachment.js b/dev/Model/ComposeAttachment.js index 540d947c1..5b7798628 100644 --- a/dev/Model/ComposeAttachment.js +++ b/dev/Model/ComposeAttachment.js @@ -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()) diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index d36a73729..d17098331 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -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 diff --git a/dev/View/Popup/OpenPgpImport.js b/dev/View/Popup/OpenPgpImport.js index f8c43951b..30b80f0e2 100644 --- a/dev/View/Popup/OpenPgpImport.js +++ b/dev/View/Popup/OpenPgpImport.js @@ -69,8 +69,8 @@ export class OpenPgpImportPopupView extends AbstractViewPopup { this.close(); } - onShow() { - this.key(''); + onShow(key) { + this.key(key || ''); this.keyError(false); this.keyErrorMessage(''); } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 1ab430df3..0c7e90cf7 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -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); } } } diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php index aedc2625b..fa77a5ee5 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -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': diff --git a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Attachment.php b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Attachment.php index a81493c41..10ef808da 100644 --- a/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Attachment.php +++ b/snappymail/v/0.0.0/app/libraries/MailSo/Mime/Attachment.php @@ -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 diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php index 32faf6442..257e99e3d 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -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 { diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php index 507ef448d..31ec11f31 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Messages.php @@ -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 ) ); } diff --git a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php index d639eea6c..0e9c78f42 100644 --- a/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php +++ b/snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Raw.php @@ -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) diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/file/magic.mime.php b/snappymail/v/0.0.0/app/libraries/snappymail/file/magic.mime.php new file mode 100644 index 000000000..2b79d0069 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/file/magic.mime.php @@ -0,0 +1,34 @@ + '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', + '#^ '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', +); diff --git a/snappymail/v/0.0.0/app/libraries/snappymail/file/mimetype.php b/snappymail/v/0.0.0/app/libraries/snappymail/file/mimetype.php new file mode 100644 index 000000000..ba44e9cd6 --- /dev/null +++ b/snappymail/v/0.0.0/app/libraries/snappymail/file/mimetype.php @@ -0,0 +1,276 @@ +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', + ]; + +}