diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index ce42a119f..66b712eb4 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -422,6 +422,8 @@ PRIVATE data/data_document.h data/data_document_media.cpp data/data_document_media.h + data/data_document_resolver.cpp + data/data_document_resolver.h data/data_drafts.cpp data/data_drafts.h data/data_folder.cpp diff --git a/Telegram/SourceFiles/boxes/background_preview_box.cpp b/Telegram/SourceFiles/boxes/background_preview_box.cpp index a3d667bfb..cf1af6627 100644 --- a/Telegram/SourceFiles/boxes/background_preview_box.cpp +++ b/Telegram/SourceFiles/boxes/background_preview_box.cpp @@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_document_resolver.h" #include "data/data_file_origin.h" #include "base/unixtime.h" #include "boxes/confirm_box.h" diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index a687acc1f..c09ea3d2e 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_document.h" +#include "data/data_document_resolver.h" #include "data/data_session.h" #include "data/data_streaming.h" #include "data/data_document_media.h" @@ -319,65 +320,7 @@ void DocumentOpenClickHandler::Open( Data::FileOrigin origin, not_null<DocumentData*> data, HistoryItem *context) { - if (!data->date) { - return; - } - - const auto media = data->createMediaView(); - const auto openImageInApp = [&] { - if (data->size >= App::kImageSizeLimit) { - return false; - } - const auto &location = data->location(true); - if (!location.isEmpty() && location.accessEnable()) { - const auto guard = gsl::finally([&] { - location.accessDisable(); - }); - const auto path = location.name(); - if (Core::MimeTypeForFile(path).name().startsWith("image/") - && QImageReader(path).canRead()) { - Core::App().showDocument(data, context); - return true; - } - } else if (data->mimeString().startsWith("image/") - && !media->bytes().isEmpty()) { - auto bytes = media->bytes(); - auto buffer = QBuffer(&bytes); - if (QImageReader(&buffer).canRead()) { - Core::App().showDocument(data, context); - return true; - } - } - return false; - }; - const auto &location = data->location(true); - if (data->isTheme() && media->loaded(true)) { - Core::App().showDocument(data, context); - location.accessDisable(); - } else if (media->canBePlayed()) { - if (data->isAudioFile() - || data->isVoiceMessage() - || data->isVideoMessage()) { - const auto msgId = context ? context->fullId() : FullMsgId(); - Media::Player::instance()->playPause({ data, msgId }); - } else if (context - && data->isAnimation() - && HistoryView::Gif::CanPlayInline(data)) { - data->owner().requestAnimationPlayInline(context); - } else { - Core::App().showDocument(data, context); - } - } else { - data->saveFromDataSilent(); - if (!openImageInApp()) { - if (!data->filepath(true).isEmpty()) { - LaunchWithWarning(&data->session(), location.name(), context); - } else if (data->status == FileReady - || data->status == FileDownloadFailed) { - DocumentSaveClickHandler::Save(origin, data); - } - } - } + Data::ResolveDocument(data, context); } void DocumentOpenClickHandler::onClickImpl() const { @@ -1634,126 +1577,3 @@ void DocumentData::collectLocalData(not_null<DocumentData*> local) { session().local().writeFileLocation(mediaKey(), _location); } } - -namespace Data { - -QString FileExtension(const QString &filepath) { - const auto reversed = ranges::views::reverse(filepath); - const auto last = ranges::find_first_of(reversed, ".\\/"); - if (last == reversed.end() || *last != '.') { - return QString(); - } - return QString(last.base(), last - reversed.begin()); -} - -bool IsValidMediaFile(const QString &filepath) { - static const auto kExtensions = [] { - const auto list = qsl("\ -16svx 2sf 3g2 3gp 8svx aac aaf aif aifc aiff amr amv ape asf ast au aup \ -avchd avi brstm bwf cam cdda cust dat divx drc dsh dsf dts dtshd dtsma \ -dvr-ms dwd evo f4a f4b f4p f4v fla flac flr flv gif gifv gsf gsm gym iff \ -ifo it jam la ly m1v m2p m2ts m2v m4a m4p m4v mcf mid mk3d mka mks mkv mng \ -mov mp1 mp2 mp3 mp4 minipsf mod mpc mpe mpeg mpg mpv mscz mt2 mus mxf mxl \ -niff nsf nsv off ofr ofs ogg ogv opus ots pac ps psf psf2 psflib ptb qsf \ -qt ra raw rka rm rmj rmvb roq s3m shn sib sid smi smp sol spc spx ssf svi \ -swa swf tak ts tta txm usf vgm vob voc vox vqf wav webm wma wmv wrap wtv \ -wv xm xml ym yuv").split(' '); - return base::flat_set<QString>(list.begin(), list.end()); - }(); - - return ranges::binary_search( - kExtensions, - FileExtension(filepath).toLower()); -} - -bool IsExecutableName(const QString &filepath) { - static const auto kExtensions = [] { - const auto joined = -#ifdef Q_OS_MAC - qsl("\ -applescript action app bin command csh osx workflow terminal url caction \ -mpkg pkg scpt scptd xhtm webarchive"); -#elif defined Q_OS_UNIX // Q_OS_MAC - qsl("bin csh deb desktop ksh out pet pkg pup rpm run sh shar \ -slp zsh"); -#else // Q_OS_MAC || Q_OS_UNIX - qsl("\ -ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \ -chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \ -grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \ -lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \ -mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \ -msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \ -php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \ -psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \ -sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \ -vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \ -vtx website ws wsc wsf wsh xbap xll xnk xs"); -#endif // !Q_OS_MAC && !Q_OS_UNIX - const auto list = joined.split(' '); - return base::flat_set<QString>(list.begin(), list.end()); - }(); - - return ranges::binary_search( - kExtensions, - FileExtension(filepath).toLower()); -} - -bool IsIpRevealingName(const QString &filepath) { - static const auto kExtensions = [] { - const auto joined = u"htm html svg"_q; - const auto list = joined.split(' '); - return base::flat_set<QString>(list.begin(), list.end()); - }(); - static const auto kMimeTypes = [] { - const auto joined = u"text/html image/svg+xml"_q; - const auto list = joined.split(' '); - return base::flat_set<QString>(list.begin(), list.end()); - }(); - - return ranges::binary_search( - kExtensions, - FileExtension(filepath).toLower() - ) || ranges::binary_search( - kMimeTypes, - QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name() - ); -} - -base::binary_guard ReadImageAsync( - not_null<Data::DocumentMedia*> media, - FnMut<QImage(QImage)> postprocess, - FnMut<void(QImage&&)> done) { - auto result = base::binary_guard(); - crl::async([ - bytes = media->bytes(), - path = media->owner()->filepath(), - postprocess = std::move(postprocess), - guard = result.make_guard(), - callback = std::move(done) - ]() mutable { - auto format = QByteArray(); - if (bytes.isEmpty()) { - QFile f(path); - if (f.size() <= App::kImageSizeLimit - && f.open(QIODevice::ReadOnly)) { - bytes = f.readAll(); - } - } - auto image = bytes.isEmpty() - ? QImage() - : App::readImage(bytes, &format, false, nullptr); - if (postprocess) { - image = postprocess(std::move(image)); - } - crl::on_main(std::move(guard), [ - image = std::move(image), - callback = std::move(callback) - ]() mutable { - callback(std::move(image)); - }); - }); - return result; -} - -} // namespace Data diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index 42eb6c2bf..92cd8ba30 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -438,16 +438,3 @@ QString DocumentFileNameForSave( bool forceSavingAs = false, const QString &already = QString(), const QDir &dir = QDir()); - -namespace Data { - -[[nodiscard]] QString FileExtension(const QString &filepath); -[[nodiscard]] bool IsValidMediaFile(const QString &filepath); -[[nodiscard]] bool IsExecutableName(const QString &filepath); -[[nodiscard]] bool IsIpRevealingName(const QString &filepath); -base::binary_guard ReadImageAsync( - not_null<Data::DocumentMedia*> media, - FnMut<QImage(QImage)> postprocess, - FnMut<void(QImage&&)> done); - -} // namespace Data diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 4a1267e6e..33fe6d556 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document_media.h" #include "data/data_document.h" +#include "data/data_document_resolver.h" #include "data/data_session.h" #include "data/data_cloud_themes.h" #include "data/data_file_origin.h" diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp new file mode 100644 index 000000000..720c4ecf6 --- /dev/null +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -0,0 +1,278 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "data/data_document_resolver.h" + +#include "app.h" +#include "facades.h" +#include "base/platform/base_platform_info.h" +#include "boxes/confirm_box.h" +#include "core/application.h" +#include "core/core_settings.h" +#include "core/mime_type.h" +#include "data/data_document.h" +#include "data/data_document_media.h" +#include "data/data_file_origin.h" +#include "data/data_session.h" +#include "history/view/media/history_view_gif.h" +#include "history/history.h" +#include "history/history_item.h" +#include "media/player/media_player_instance.h" +#include "platform/platform_file_utilities.h" +#include "ui/text/text_utilities.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" + +#include <QtCore/QBuffer> +#include <QtCore/QMimeType> +#include <QtCore/QMimeDatabase> + +namespace Data { +namespace { + +void LaunchWithWarning( + // not_null<Window::Controller*> controller, + const QString &name, + HistoryItem *item) { + const auto isExecutable = Data::IsExecutableName(name); + const auto isIpReveal = Data::IsIpRevealingName(name); + auto &app = Core::App(); + const auto warn = [&] { + if (item && item->history()->peer->isVerified()) { + return false; + } + return (isExecutable && app.settings().exeLaunchWarning()) + || (isIpReveal && app.settings().ipRevealWarning()); + }(); + const auto extension = '.' + Data::FileExtension(name); + if (Platform::IsWindows() && extension == u"."_q) { + // If you launch a file without extension, like "test", in case + // there is an executable file with the same name in this folder, + // like "test.bat", the executable file will be launched. + // + // Now we always force an Open With dialog box for such files. + crl::on_main([=] { + Platform::File::UnsafeShowOpenWith(name); + }); + return; + } else if (!warn) { + File::Launch(name); + return; + } + const auto callback = [=, &app](bool checked) { + if (checked) { + if (isExecutable) { + app.settings().setExeLaunchWarning(false); + } else if (isIpReveal) { + app.settings().setIpRevealWarning(false); + } + app.saveSettingsDelayed(); + } + File::Launch(name); + }; + auto text = isExecutable + ? tr::lng_launch_exe_warning( + lt_extension, + rpl::single(Ui::Text::Bold(extension)), + Ui::Text::WithEntities) + : tr::lng_launch_svg_warning(Ui::Text::WithEntities); + Ui::show(Box<ConfirmDontWarnBox>( + std::move(text), + tr::lng_launch_exe_dont_ask(tr::now), + (isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(), + callback)); +} + +} // namespace + +QString FileExtension(const QString &filepath) { + const auto reversed = ranges::views::reverse(filepath); + const auto last = ranges::find_first_of(reversed, ".\\/"); + if (last == reversed.end() || *last != '.') { + return QString(); + } + return QString(last.base(), last - reversed.begin()); +} + +// bool IsValidMediaFile(const QString &filepath) { +// static const auto kExtensions = [] { +// const auto list = qsl("\ +// 16svx 2sf 3g2 3gp 8svx aac aaf aif aifc aiff amr amv ape asf ast au aup \ +// avchd avi brstm bwf cam cdda cust dat divx drc dsh dsf dts dtshd dtsma \ +// dvr-ms dwd evo f4a f4b f4p f4v fla flac flr flv gif gifv gsf gsm gym iff \ +// ifo it jam la ly m1v m2p m2ts m2v m4a m4p m4v mcf mid mk3d mka mks mkv mng \ +// mov mp1 mp2 mp3 mp4 minipsf mod mpc mpe mpeg mpg mpv mscz mt2 mus mxf mxl \ +// niff nsf nsv off ofr ofs ogg ogv opus ots pac ps psf psf2 psflib ptb qsf \ +// qt ra raw rka rm rmj rmvb roq s3m shn sib sid smi smp sol spc spx ssf svi \ +// swa swf tak ts tta txm usf vgm vob voc vox vqf wav webm wma wmv wrap wtv \ +// wv xm xml ym yuv").split(' '); +// return base::flat_set<QString>(list.begin(), list.end()); +// }(); + +// return ranges::binary_search( +// kExtensions, +// FileExtension(filepath).toLower()); +// } + +bool IsExecutableName(const QString &filepath) { + static const auto kExtensions = [] { + const auto joined = +#ifdef Q_OS_MAC + qsl("\ +applescript action app bin command csh osx workflow terminal url caction \ +mpkg pkg scpt scptd xhtm webarchive"); +#elif defined Q_OS_UNIX // Q_OS_MAC + qsl("bin csh deb desktop ksh out pet pkg pup rpm run sh shar \ +slp zsh"); +#else // Q_OS_MAC || Q_OS_UNIX + qsl("\ +ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \ +chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \ +grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \ +lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \ +mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \ +msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \ +php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \ +psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \ +sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \ +vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \ +vtx website ws wsc wsf wsh xbap xll xnk xs"); +#endif // !Q_OS_MAC && !Q_OS_UNIX + const auto list = joined.split(' '); + return base::flat_set<QString>(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower()); +} + +bool IsIpRevealingName(const QString &filepath) { + static const auto kExtensions = [] { + const auto joined = u"htm html svg"_q; + const auto list = joined.split(' '); + return base::flat_set<QString>(list.begin(), list.end()); + }(); + static const auto kMimeTypes = [] { + const auto joined = u"text/html image/svg+xml"_q; + const auto list = joined.split(' '); + return base::flat_set<QString>(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower() + ) || ranges::binary_search( + kMimeTypes, + QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name() + ); +} + +base::binary_guard ReadImageAsync( + not_null<Data::DocumentMedia*> media, + FnMut<QImage(QImage)> postprocess, + FnMut<void(QImage&&)> done) { + auto result = base::binary_guard(); + crl::async([ + bytes = media->bytes(), + path = media->owner()->filepath(), + postprocess = std::move(postprocess), + guard = result.make_guard(), + callback = std::move(done) + ]() mutable { + auto format = QByteArray(); + if (bytes.isEmpty()) { + QFile f(path); + if (f.size() <= App::kImageSizeLimit + && f.open(QIODevice::ReadOnly)) { + bytes = f.readAll(); + } + } + auto image = bytes.isEmpty() + ? QImage() + : App::readImage(bytes, &format, false, nullptr); + if (postprocess) { + image = postprocess(std::move(image)); + } + crl::on_main(std::move(guard), [ + image = std::move(image), + callback = std::move(callback) + ]() mutable { + callback(std::move(image)); + }); + }); + return result; +} + +void ResolveDocument( + // not_null<Window::Controller*> controller, + not_null<DocumentData*> document, + HistoryItem *item) { + if (!document->date) { + return; + } + + const auto media = document->createMediaView(); + const auto openImageInApp = [&] { + if (document->size >= App::kImageSizeLimit) { + return false; + } + const auto &location = document->location(true); + if (!location.isEmpty() && location.accessEnable()) { + const auto guard = gsl::finally([&] { + location.accessDisable(); + }); + const auto path = location.name(); + if (Core::MimeTypeForFile(path).name().startsWith("image/") + && QImageReader(path).canRead()) { + Core::App().showDocument(document, item); + return true; + } + } else if (document->mimeString().startsWith("image/") + && !media->bytes().isEmpty()) { + auto bytes = media->bytes(); + auto buffer = QBuffer(&bytes); + if (QImageReader(&buffer).canRead()) { + Core::App().showDocument(document, item); + return true; + } + } + return false; + }; + const auto &location = document->location(true); + if (document->isTheme() && media->loaded(true)) { + Core::App().showDocument(document, item); + location.accessDisable(); + } else if (media->canBePlayed()) { + if (document->isAudioFile() + || document->isVoiceMessage() + || document->isVideoMessage()) { + const auto msgId = item ? item->fullId() : FullMsgId(); + ::Media::Player::instance()->playPause({ document, msgId }); + } else if (item + && document->isAnimation() + && HistoryView::Gif::CanPlayInline(document)) { + document->owner().requestAnimationPlayInline(item); + } else { + Core::App().showDocument(document, item); + } + } else { + document->saveFromDataSilent(); + if (!openImageInApp()) { + if (!document->filepath(true).isEmpty()) { + LaunchWithWarning(location.name(), item); + } else if (document->status == FileReady + || document->status == FileDownloadFailed) { + DocumentSaveClickHandler::Save( + item ? item->fullId() : Data::FileOrigin(), + document); + } + } + } +} + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_document_resolver.h b/Telegram/SourceFiles/data/data_document_resolver.h new file mode 100644 index 000000000..d52c50eb0 --- /dev/null +++ b/Telegram/SourceFiles/data/data_document_resolver.h @@ -0,0 +1,37 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "base/binary_guard.h" + +class DocumentData; +class HistoryItem; + +namespace Window { +class Controller; +} // namespace Window + +namespace Data { + +class DocumentMedia; + +[[nodiscard]] QString FileExtension(const QString &filepath); +// [[nodiscard]] bool IsValidMediaFile(const QString &filepath); +[[nodiscard]] bool IsExecutableName(const QString &filepath); +[[nodiscard]] bool IsIpRevealingName(const QString &filepath); +base::binary_guard ReadImageAsync( + not_null<Data::DocumentMedia*> media, + FnMut<QImage(QImage)> postprocess, + FnMut<void(QImage&&)> done); + +void ResolveDocument( + // not_null<Window::Controller*> controller, + not_null<DocumentData*> document, + HistoryItem *item); + +} // namespace Data diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index ede2ea5ae..c48f410ca 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_document_resolver.h" #include "data/data_media_types.h" #include "data/data_file_origin.h" #include "styles/style_chat.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 4d094ba41..1d7fc40e9 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_photo.h" #include "data/data_document.h" #include "data/data_document_media.h" +#include "data/data_document_resolver.h" #include "data/data_web_page.h" #include "data/data_game.h" #include "data/data_peer_values.h" diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index bae062b05..b0d43e532 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "overview/overview_layout_delegate.h" #include "data/data_document.h" +#include "data/data_document_resolver.h" #include "data/data_session.h" #include "data/data_web_page.h" #include "data/data_media_types.h"