From 6a28cd1a35d5185cc3fdb6f8636d6a3251f1cf5f Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 15 Apr 2024 11:18:57 +0400 Subject: [PATCH] Reimplement file open confirmations. --- Telegram/Resources/langs/lang.strings | 11 +- Telegram/SourceFiles/boxes/ringtones_box.cpp | 2 +- Telegram/SourceFiles/core/core_settings.cpp | 30 +++- Telegram/SourceFiles/core/core_settings.h | 11 +- Telegram/SourceFiles/core/mime_type.cpp | 141 +++++++++++++--- Telegram/SourceFiles/core/mime_type.h | 17 ++ Telegram/SourceFiles/data/data_cloud_file.cpp | 8 + Telegram/SourceFiles/data/data_cloud_file.h | 2 + Telegram/SourceFiles/data/data_document.cpp | 51 +++++- Telegram/SourceFiles/data/data_document.h | 6 + .../SourceFiles/data/data_document_media.cpp | 5 +- .../data/data_document_resolver.cpp | 150 ++++++------------ .../SourceFiles/data/data_document_resolver.h | 4 - .../view/media/history_view_document.cpp | 9 +- .../main/main_session_settings.cpp | 5 +- .../SourceFiles/overview/overview_layout.cpp | 4 +- .../settings/settings_privacy_security.cpp | 77 ++++++++- 17 files changed, 375 insertions(+), 158 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 74b1518abd..11b1a75e88 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -734,6 +734,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_sensitive_disable_filtering" = "Disable filtering"; "lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices."; "lng_settings_security_bots" = "Bots and websites"; +"lng_settings_file_confirmations" = "File open confirmations"; +"lng_settings_edit_extensions" = "Extensions whitelist"; +"lng_settings_edit_extensions_about" = "Open files with the following extensions without additional confirmation."; +"lng_settings_edit_ip_confirm" = "IP reveal warning"; +"lng_settings_edit_ip_confirm_about" = "Show confirmation when opening files that may reveal your IP address."; "lng_settings_clear_payment_info" = "Clear Payment and Shipping Info"; "lng_settings_logged_in" = "Connected websites"; "lng_settings_logged_in_title" = "Logged in with Telegram"; @@ -4480,10 +4485,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_translate_settings_about" = "The 'Translate' button will appear when you open a context menu on a text message."; "lng_translate_settings_one" = "Please choose at least one language so that it can be used as the \"Translate to\" language."; -"lng_launch_exe_warning" = "This file has a {extension} extension.\nAre you sure you want to run it?"; +"lng_launch_exe_warning" = "This file has {extension} extension.\nAre you sure you want to run it?"; +"lng_launch_other_warning" = "This file has {extension} extension.\nAre you sure you want to open it?"; "lng_launch_svg_warning" = "Opening this file can potentially expose your IP address to its sender. Continue?"; "lng_launch_exe_sure" = "Run"; +"lng_launch_other_sure" = "Open"; "lng_launch_exe_dont_ask" = "Don't ask me again"; +"lng_launch_dont_ask" = "Remember for this file type"; +"lng_launch_dont_ask_settings" = "You can later edit trusted file types in Settings > Privacy and Security > File open confirmations."; "lng_polls_anonymous" = "Anonymous Poll"; "lng_polls_public" = "Poll"; diff --git a/Telegram/SourceFiles/boxes/ringtones_box.cpp b/Telegram/SourceFiles/boxes/ringtones_box.cpp index d8097222e9..4c299c162b 100644 --- a/Telegram/SourceFiles/boxes/ringtones_box.cpp +++ b/Telegram/SourceFiles/boxes/ringtones_box.cpp @@ -90,7 +90,7 @@ QString ExtractRingtoneName(not_null document) { } const auto name = document->filename(); if (!name.isEmpty()) { - const auto extension = Data::FileExtension(name); + const auto extension = Core::FileExtension(name); if (extension.isEmpty()) { return name; } else if (name.size() > extension.size() + 1) { diff --git a/Telegram/SourceFiles/core/core_settings.cpp b/Telegram/SourceFiles/core/core_settings.cpp index b4f7708769..faa41445f0 100644 --- a/Telegram/SourceFiles/core/core_settings.cpp +++ b/Telegram/SourceFiles/core/core_settings.cpp @@ -156,6 +156,10 @@ QByteArray Settings::serialize() const { const auto &recentEmojiPreloadData = _recentEmojiPreload.empty() ? recentEmojiPreloadGenerated : _recentEmojiPreload; + const auto noWarningExtensions = QStringList( + begin(_noWarningExtensions), + end(_noWarningExtensions) + ).join(' '); auto size = Serialize::bytearraySize(themesAccentColors) + sizeof(qint32) * 5 @@ -212,7 +216,8 @@ QByteArray Settings::serialize() const { + Serialize::stringSize(_captureDeviceId.current()) + Serialize::stringSize(_callPlaybackDeviceId.current()) + Serialize::stringSize(_callCaptureDeviceId.current()) - + Serialize::bytearraySize(ivPosition); + + Serialize::bytearraySize(ivPosition) + + Serialize::stringSize(noWarningExtensions); auto result = QByteArray(); result.reserve(size); @@ -252,7 +257,7 @@ QByteArray Settings::serialize() const { << qint32(_sendSubmitWay) << qint32(_includeMutedCounter ? 1 : 0) << qint32(_countUnreadMessages ? 1 : 0) - << qint32(_exeLaunchWarning ? 1 : 0) + << qint32(1) // legacy exe launch warning << qint32(_notifyAboutPinned.current() ? 1 : 0) << qint32(_loopAnimatedStickers ? 1 : 0) << qint32(_largeEmoji.current() ? 1 : 0) @@ -357,7 +362,8 @@ QByteArray Settings::serialize() const { << _captureDeviceId.current() << _callPlaybackDeviceId.current() << _callCaptureDeviceId.current() - << ivPosition; + << ivPosition + << noWarningExtensions; } Ensures(result.size() == size); @@ -406,7 +412,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) { qint32 sendSubmitWay = static_cast(_sendSubmitWay); qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0; qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0; - qint32 exeLaunchWarning = _exeLaunchWarning ? 1 : 0; + std::optional noWarningExtensions; + qint32 legacyExeLaunchWarning = 1; qint32 notifyAboutPinned = _notifyAboutPinned.current() ? 1 : 0; qint32 loopAnimatedStickers = _loopAnimatedStickers ? 1 : 0; qint32 largeEmoji = _largeEmoji.current() ? 1 : 0; @@ -513,7 +520,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) { >> sendSubmitWay >> includeMutedCounter >> countUnreadMessages - >> exeLaunchWarning + >> legacyExeLaunchWarning >> notifyAboutPinned >> loopAnimatedStickers >> largeEmoji @@ -755,6 +762,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) { if (!stream.atEnd()) { stream >> ivPosition; } + if (!stream.atEnd()) { + noWarningExtensions = QString(); + stream >> *noWarningExtensions; + } if (stream.status() != QDataStream::Ok) { LOG(("App Error: " "Bad data for Core::Settings::constructFromSerialized()")); @@ -817,7 +828,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) { } _includeMutedCounter = (includeMutedCounter == 1); _countUnreadMessages = (countUnreadMessages == 1); - _exeLaunchWarning = (exeLaunchWarning == 1); + if (noWarningExtensions) { + const auto list = noWarningExtensions->mid(0, 10240) + .split(' ', Qt::SkipEmptyParts) + .mid(0, 1024); + _noWarningExtensions = base::flat_set(list.begin(), list.end()); + } _ipRevealWarning = (ipRevealWarning == 1); _notifyAboutPinned = (notifyAboutPinned == 1); _loopAnimatedStickers = (loopAnimatedStickers == 1); @@ -1283,7 +1299,7 @@ void Settings::resetOnLastLogout() { //_sendSubmitWay = Ui::InputSubmitSettings::Enter; _soundOverrides = {}; - _exeLaunchWarning = true; + _noWarningExtensions.clear(); _ipRevealWarning = true; _loopAnimatedStickers = true; _largeEmoji = true; diff --git a/Telegram/SourceFiles/core/core_settings.h b/Telegram/SourceFiles/core/core_settings.h index 12b7fa431d..cded2ec200 100644 --- a/Telegram/SourceFiles/core/core_settings.h +++ b/Telegram/SourceFiles/core/core_settings.h @@ -398,11 +398,12 @@ public: } [[nodiscard]] QString getSoundPath(const QString &key) const; - [[nodiscard]] bool exeLaunchWarning() const { - return _exeLaunchWarning; + [[nodiscard]] auto noWarningExtensions() const + -> const base::flat_set & { + return _noWarningExtensions; } - void setExeLaunchWarning(bool warning) { - _exeLaunchWarning = warning; + void setNoWarningExtensions(base::flat_set extensions) { + _noWarningExtensions = std::move(extensions); } [[nodiscard]] bool ipRevealWarning() const { return _ipRevealWarning; @@ -933,7 +934,7 @@ private: Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay(); Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings(); base::flat_map _soundOverrides; - bool _exeLaunchWarning = true; + base::flat_set _noWarningExtensions; bool _ipRevealWarning = true; bool _loopAnimatedStickers = true; rpl::variable _largeEmoji = true; diff --git a/Telegram/SourceFiles/core/mime_type.cpp b/Telegram/SourceFiles/core/mime_type.cpp index 8ba1b64945..7467145cf5 100644 --- a/Telegram/SourceFiles/core/mime_type.cpp +++ b/Telegram/SourceFiles/core/mime_type.cpp @@ -37,6 +37,12 @@ namespace { && data->hasImage(); } +[[nodiscard]] base::flat_set SplitExtensions( + const QString &joined) { + const auto list = joined.split(' '); + return base::flat_set(list.begin(), list.end()); +} + } // namespace MimeType::MimeType(const QMimeType &type) : _typeStruct(type) { @@ -162,22 +168,9 @@ bool IsMimeAcceptedForPhotoVideoAlbum(const QString &mime) { } bool FileIsImage(const QString &name, const QString &mime) { - QString lowermime = mime.toLower(), namelower = name.toLower(); - if (lowermime.startsWith(u"image/"_q)) { - return true; - } else if (namelower.endsWith(u".bmp"_q) - || namelower.endsWith(u".jpg"_q) - || namelower.endsWith(u".jpeg"_q) - || namelower.endsWith(u".gif"_q) - || namelower.endsWith(u".webp"_q) - || namelower.endsWith(u".tga"_q) - || namelower.endsWith(u".tiff"_q) - || namelower.endsWith(u".tif"_q) - || namelower.endsWith(u".psd"_q) - || namelower.endsWith(u".png"_q)) { - return true; - } - return false; + return name.isEmpty() + ? mime.toLower().startsWith(u"image/"_q) + : (DetectNameType(name) == NameType::Image); } std::shared_ptr ShareMimeMediaData( @@ -194,10 +187,10 @@ std::shared_ptr ShareMimeMediaData( result->setData(u"application/x-td-use-jpeg"_q, "1"); result->setData(u"image/jpeg"_q, original->data(u"image/jpeg"_q)); } - if (auto list = Core::ReadMimeUrls(original); !list.isEmpty()) { + if (auto list = ReadMimeUrls(original); !list.isEmpty()) { result->setUrls(std::move(list)); } - result->setText(Core::ReadMimeText(original)); + result->setText(ReadMimeText(original)); return result; } @@ -240,4 +233,116 @@ bool CanSendFiles(not_null data) { return false; } +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()); +} + +NameType DetectNameType(const QString &filepath) { + static const auto kImage = SplitExtensions(u"\ +afdesign ai avif bmp dng gif heic icns ico jfif jpeg jpg jpg-large nef png \ +png-large psd raw sketch svg tga tif tiff webp"_q); + static const auto kVideo = SplitExtensions(u"\ +3g2 3gp 3gpp aep avi flv h264 m4s m4v mkv mov mp4 mpeg mpg ogv srt tgs tgv \ +vob webm wmv"_q); + static const auto kAudio = SplitExtensions(u"\ +aac ac3 aif amr caf cda cue flac m4a m4b mid midi mp3 ogg opus wav wma"_q); + static const auto kDocument = SplitExtensions(u"\ +pdf doc docx ppt pptx pps ppsx xls xlsx txt rtf odt ods odp csv text log tl \ +tex xspf xml djvu diag ps ost kml pub epub mobi cbr cbz fb2 prc ris pem p7b \ +m3u m3u8 wpd wpl htm html xhtml key"_q); + static const auto kArchive = SplitExtensions(u"\ +7z arj bz2 gz rar tar xz z zip zst"_q); + static const auto kThemeFile = SplitExtensions(u"\ +tdesktop-theme tdesktop-palette tgios-theme attheme"_q); + static const auto kOtherBenign = SplitExtensions(u"\ +c cc cpp cxx h m mm swift cs ts class java css ninja cmake patch diff plist \ +gyp gitignore strings asoundrc torrent csr json xaml md keylayout sql \ +sln xib mk \ +\ +dmg img iso vcd \ +\ +pdb eot ics ips ipa core mem pcap ovpn part pcapng dmp pkpass dat zxp crash \ +file bak gbr plain dlc fon fnt otf ttc ttf gpx db rss cur \ +\ +tdesktop-endpoints"_q); + + static const auto kExecutable = SplitExtensions( +#ifdef Q_OS_WIN + u"\ +ad ade adp ahk app application appref-ms asp aspx asx bas bat bin cab cdxml \ +cer cfg cgi chi chm cmd cnt com conf 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 jsp key ksh lexe library-ms 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 pyzw pyz rb reg rgs scf scr sct search-ms settingcontent-ms \ +sh shb shs slk sys swf t tmp u3p url vb vbe vbp vbs vbscript vdx vsmacros \ +vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx vtx website wlua ws wsc \ +wsf wsh xbap xll xlsm xnk xs"_q +#elif defined Q_OS_MAC // Q_OS_MAC + u"\ +applescript action app bin command csh osx workflow terminal url caction \ +mpkg pkg scpt scptd xhtm xhtml webarchive"_q +#else // Q_OS_WIN || Q_OS_MAC + u"bin csh deb desktop ksh out pet pkg pup rpm run sh shar slp zsh"_q +#endif // !Q_OS_WIN && !Q_OS_MAC + ); + + const auto extension = FileExtension(filepath).toLower(); + if (kExecutable.contains(extension)) { + return NameType::Executable; + } else if (kImage.contains(extension)) { + return NameType::Image; + } else if (kVideo.contains(extension)) { + return NameType::Video; + } else if (kAudio.contains(extension)) { + return NameType::Audio; + } else if (kDocument.contains(extension)) { + return NameType::Document; + } else if (kArchive.contains(extension)) { + return NameType::Archive; + } else if (kThemeFile.contains(extension)) { + return NameType::ThemeFile; + } else if (kOtherBenign.contains(extension)) { + return NameType::OtherBenign; + } + return NameType::Unknown; +} + +bool NameTypeAllowsThumbnail(NameType type) { + return type == NameType::Image + || type == NameType::Video + || type == NameType::Audio + || type == NameType::Document + || type == NameType::ThemeFile; +} + +bool IsIpRevealingPath(const QString &filepath) { + static const auto kExtensions = [] { + const auto joined = u"htm html svg m4v m3u8 xhtml"_q; + const auto list = joined.split(' '); + return base::flat_set(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(list.begin(), list.end()); + }(); + + return ranges::binary_search( + kExtensions, + FileExtension(filepath).toLower() + ) || ranges::binary_search( + kMimeTypes, + QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name() + ); +} + } // namespace Core diff --git a/Telegram/SourceFiles/core/mime_type.h b/Telegram/SourceFiles/core/mime_type.h index 3271adafe8..ebf4db64be 100644 --- a/Telegram/SourceFiles/core/mime_type.h +++ b/Telegram/SourceFiles/core/mime_type.h @@ -69,4 +69,21 @@ struct MimeImageData { [[nodiscard]] QList ReadMimeUrls(not_null data); [[nodiscard]] bool CanSendFiles(not_null data); +enum class NameType : uchar { + Unknown, + Executable, + Image, + Video, + Audio, + Document, + Archive, + ThemeFile, + OtherBenign, +}; + +[[nodiscard]] QString FileExtension(const QString &filepath); +[[nodiscard]] NameType DetectNameType(const QString &filepath); +[[nodiscard]] bool NameTypeAllowsThumbnail(NameType type); +[[nodiscard]] bool IsIpRevealingPath(const QString &filepath); + } // namespace Core diff --git a/Telegram/SourceFiles/data/data_cloud_file.cpp b/Telegram/SourceFiles/data/data_cloud_file.cpp index bee90358a5..526dd89042 100644 --- a/Telegram/SourceFiles/data/data_cloud_file.cpp +++ b/Telegram/SourceFiles/data/data_cloud_file.cpp @@ -22,6 +22,14 @@ CloudFile::~CloudFile() { base::take(loader); } +void CloudFile::clear() { + location = {}; + base::take(loader); + byteSize = 0; + progressivePartSize = 0; + flags = {}; +} + CloudImage::CloudImage() = default; CloudImage::CloudImage( diff --git a/Telegram/SourceFiles/data/data_cloud_file.h b/Telegram/SourceFiles/data/data_cloud_file.h index e71cbe462c..b44a35c7d9 100644 --- a/Telegram/SourceFiles/data/data_cloud_file.h +++ b/Telegram/SourceFiles/data/data_cloud_file.h @@ -37,6 +37,8 @@ struct CloudFile final { ~CloudFile(); + void clear(); + ImageLocation location; std::unique_ptr loader; int byteSize = 0; diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp index afe91b8a17..2bf1a0104a 100644 --- a/Telegram/SourceFiles/data/data_document.cpp +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -478,6 +478,31 @@ void DocumentData::setattributes( _additional = nullptr; } + if (!_filename.isEmpty()) { + using Type = Core::NameType; + if (type == VideoDocument + || type == AnimatedDocument + || type == RoundVideoDocument + || isAnimation()) { + if (_nameType != Type::Video) { + type = FileDocument; + _additional = nullptr; + } + } + if (type == SongDocument || type == VoiceDocument || isAudioFile()) { + if (_nameType != Type::Audio) { + type = FileDocument; + _additional = nullptr; + } + } + if (!Core::NameTypeAllowsThumbnail(_nameType)) { + _inlineThumbnailBytes = {}; + _flags &= ~Flag::InlineThumbnailIsPath; + _thumbnail.clear(); + _videoThumbnail.clear(); + } + } + if (isAudioFile() || isAnimation() || isVoiceMessage() @@ -530,6 +555,10 @@ void DocumentData::updateThumbnails( const ImageWithLocation &thumbnail, const ImageWithLocation &videoThumbnail, bool isPremiumSticker) { + if (!_filename.isEmpty() + && !Core::NameTypeAllowsThumbnail(Core::DetectNameType(_filename))) { + return; + } if (!inlineThumbnail.bytes.isEmpty() && _inlineThumbnailBytes.isEmpty()) { _inlineThumbnailBytes = inlineThumbnail.bytes; @@ -919,6 +948,7 @@ void DocumentData::setFileName(const QString &remoteFileName) { for (const auto &ch : controls) { _filename = std::move(_filename).replace(ch, "_"); } + _nameType = Core::DetectNameType(_filename); } void DocumentData::setLoadedInMediaCacheLocation() { @@ -1460,6 +1490,10 @@ QString DocumentData::filename() const { return _filename; } +Core::NameType DocumentData::nameType() const { + return _nameType; +} + QString DocumentData::mimeString() const { return _mimeString; } @@ -1527,7 +1561,10 @@ bool DocumentData::isVideoMessage() const { bool DocumentData::isAnimation() const { return (type == AnimatedDocument) || isVideoMessage() - || (hasMimeType(u"image/gif"_q) + || ((_filename.isEmpty() + || _nameType == Core::NameType::Image + || _nameType == Core::NameType::Video) + && hasMimeType(u"image/gif"_q) && !(_flags & Flag::StreamingPlaybackFailed)); } @@ -1537,9 +1574,11 @@ bool DocumentData::isGifv() const { } bool DocumentData::isTheme() const { - return hasMimeType(u"application/x-tgtheme-tdesktop"_q) - || _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive) - || _filename.endsWith(u".tdesktop-palette"_q, Qt::CaseInsensitive); + return _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive) + || _filename.endsWith(u".tdesktop-palette"_q, Qt::CaseInsensitive) + || (hasMimeType(u"application/x-tgtheme-tdesktop"_q) + && (_filename.isEmpty() + || _nameType == Core::NameType::ThemeFile)); } bool DocumentData::isSong() const { @@ -1562,6 +1601,10 @@ bool DocumentData::isAudioFile() const { return true; } return false; + } else if (!_filename.isEmpty() + && _nameType != Core::NameType::Audio + && _nameType != Core::NameType::Video) { + return false; } const auto left = _mimeString.mid(prefix.size()); const auto types = { u"x-wav"_q, u"wav"_q, u"mp4"_q }; diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h index c33c9cbd0b..299a6caf1a 100644 --- a/Telegram/SourceFiles/data/data_document.h +++ b/Telegram/SourceFiles/data/data_document.h @@ -20,6 +20,10 @@ namespace Images { class Source; } // namespace Images +namespace Core { +enum class NameType : uchar; +} // namespace Core + namespace Storage { namespace Cache { struct Key; @@ -255,6 +259,7 @@ public: void collectLocalData(not_null local); [[nodiscard]] QString filename() const; + [[nodiscard]] Core::NameType nameType() const; [[nodiscard]] QString mimeString() const; [[nodiscard]] bool hasMimeType(const QString &mime) const; void setMimeString(const QString &mime); @@ -369,6 +374,7 @@ private: std::unique_ptr _additional; mutable Flags _flags = kStreamingSupportedUnknown; GoodThumbnailState _goodThumbnailState = GoodThumbnailState(); + Core::NameType _nameType = Core::NameType(); std::unique_ptr _loader; }; diff --git a/Telegram/SourceFiles/data/data_document_media.cpp b/Telegram/SourceFiles/data/data_document_media.cpp index 101f0f8cc6..e61030302b 100644 --- a/Telegram/SourceFiles/data/data_document_media.cpp +++ b/Telegram/SourceFiles/data/data_document_media.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/themes/window_theme_preview.h" #include "core/core_settings.h" #include "core/application.h" +#include "core/mime_type.h" #include "storage/file_download.h" #include "ui/chat/attach/attach_prepare.h" @@ -295,10 +296,12 @@ void DocumentMedia::automaticLoad( // No automatic download in this case. return; } + const auto indata = _owner->filename(); const auto filename = toCache ? QString() : DocumentFileNameForSave(_owner); - const auto shouldLoadFromCloud = !Data::IsExecutableName(filename) + const auto shouldLoadFromCloud = (indata.isEmpty() + || Core::DetectNameType(indata) != Core::NameType::Executable) && (item ? Data::AutoDownload::Should( _owner->session().settings().autoDownload(), diff --git a/Telegram/SourceFiles/data/data_document_resolver.cpp b/Telegram/SourceFiles/data/data_document_resolver.cpp index be68c24760..575d695cf9 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.cpp +++ b/Telegram/SourceFiles/data/data_document_resolver.cpp @@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/chat/chat_theme.h" #include "ui/text/text_utilities.h" #include "ui/widgets/checkbox.h" +#include "ui/wrap/slide_wrap.h" #include "window/window_session_controller.h" #include "styles/style_layers.h" @@ -46,11 +47,12 @@ base::options::toggle OptionExternalVideoPlayer({ void ConfirmDontWarnBox( not_null box, rpl::producer &&text, + rpl::producer &&check, rpl::producer &&confirm, Fn callback) { auto checkbox = object_ptr( box.get(), - tr::lng_launch_exe_dont_ask(), + std::move(check), false, st::defaultBoxCheckbox); const auto weak = Ui::MakeWeak(checkbox.data()); @@ -67,29 +69,43 @@ void ConfirmDontWarnBox( auto padding = st::boxPadding; padding.setTop(padding.bottom()); box->addRow(std::move(checkbox), std::move(padding)); + box->addRow(object_ptr>( + box, + object_ptr( + box, + tr::lng_launch_dont_ask_settings(), + st::boxLabel) + ))->toggleOn(weak->checkedValue()); } void LaunchWithWarning( // not_null controller, const QString &name, HistoryItem *item) { - const auto isExecutable = Data::IsExecutableName(name); - const auto isIpReveal = Data::IsIpRevealingName(name); + const auto nameType = Core::DetectNameType(name); + const auto isIpReveal = (nameType != Core::NameType::Executable) + && Core::IsIpRevealingPath(name); + const auto extension = Core::FileExtension(name).toLower(); + auto &app = Core::App(); + auto &settings = app.settings(); const auto warn = [&] { if (item && item->history()->peer->isVerified()) { return false; } - return (isExecutable && app.settings().exeLaunchWarning()) - || (isIpReveal && app.settings().ipRevealWarning()); + return (isIpReveal && settings.ipRevealWarning()) + || ((nameType == Core::NameType::Executable + || nameType == Core::NameType::Unknown) + && !settings.noWarningExtensions().contains(extension)); }(); - const auto extension = '.' + Data::FileExtension(name); - if (Platform::IsWindows() && extension == u"."_q) { + if (extension.isEmpty()) { // 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. + // + // Let's force it for all platforms for files without extension. crl::on_main([=] { Platform::File::UnsafeShowOpenWith(name); }); @@ -98,27 +114,38 @@ void LaunchWithWarning( File::Launch(name); return; } - const auto callback = [=, &app](bool checked) { + const auto callback = [=, &app, &settings](bool checked) { if (checked) { - if (isExecutable) { - app.settings().setExeLaunchWarning(false); - } else if (isIpReveal) { - app.settings().setIpRevealWarning(false); + if (isIpReveal) { + settings.setIpRevealWarning(false); + } else { + auto copy = settings.noWarningExtensions(); + copy.emplace(extension); + settings.setNoWarningExtensions(std::move(copy)); } 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); + auto text = isIpReveal + ? tr::lng_launch_svg_warning(Ui::Text::WithEntities) + : ((nameType == Core::NameType::Executable) + ? tr::lng_launch_exe_warning + : tr::lng_launch_other_warning)( + lt_extension, + rpl::single(Ui::Text::Bold('.' + extension)), + Ui::Text::WithEntities); + auto check = (isIpReveal + ? tr::lng_launch_exe_dont_ask + : tr::lng_launch_dont_ask)(); + auto confirm = ((nameType == Core::NameType::Executable) + ? tr::lng_launch_exe_sure + : tr::lng_launch_other_sure)(); Ui::show(Box( ConfirmDontWarnBox, std::move(text), - (isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(), + std::move(check), + std::move(confirm), callback)); } @@ -126,91 +153,6 @@ void LaunchWithWarning( const char kOptionExternalVideoPlayer[] = "external-video-player"; -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()); -} - -#if 0 -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(list.begin(), list.end()); - }(); - - return ranges::binary_search( - kExtensions, - FileExtension(filepath).toLower()); -} -#endif - -bool IsExecutableName(const QString &filepath) { - static const auto kExtensions = [] { - const auto joined = -#ifdef Q_OS_WIN - u"\ -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 \ -lexe 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 pyzw 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 wlua ws wsc wsf wsh xbap xll xnk xs"_q; -#elif defined Q_OS_MAC // Q_OS_MAC - u"\ -applescript action app bin command csh osx workflow terminal url caction \ -mpkg pkg scpt scptd xhtm webarchive"_q; -#else // Q_OS_WIN || Q_OS_MAC - u"bin csh deb desktop ksh out pet pkg pup rpm run sh shar \ -slp zsh"_q; -#endif // !Q_OS_WIN && !Q_OS_MAC - const auto list = joined.split(' '); - return base::flat_set(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 m4v m3u8"_q; - const auto list = joined.split(' '); - return base::flat_set(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(list.begin(), list.end()); - }(); - - return ranges::binary_search( - kExtensions, - FileExtension(filepath).toLower() - ) || ranges::binary_search( - kMimeTypes, - QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name() - ); -} - base::binary_guard ReadBackgroundImageAsync( not_null media, FnMut postprocess, diff --git a/Telegram/SourceFiles/data/data_document_resolver.h b/Telegram/SourceFiles/data/data_document_resolver.h index 9931da3e80..4988297aaa 100644 --- a/Telegram/SourceFiles/data/data_document_resolver.h +++ b/Telegram/SourceFiles/data/data_document_resolver.h @@ -22,10 +22,6 @@ class DocumentMedia; extern const char kOptionExternalVideoPlayer[]; -[[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 ReadBackgroundImageAsync( not_null media, FnMut postprocess, diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index d884580d57..b7714d5792 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -380,12 +380,9 @@ void Document::createComponents(bool caption) { mask |= HistoryDocumentVoice::Bit(); } else { mask |= HistoryDocumentNamed::Bit(); - if (_data->hasThumbnail()) { - if (!_data->isSong() - && !Data::IsExecutableName(_data->filename())) { - _data->loadThumbnail(_realParent->fullId()); - mask |= HistoryDocumentThumbed::Bit(); - } + if (_data->hasThumbnail() && !_data->isSong()) { + _data->loadThumbnail(_realParent->fullId()); + mask |= HistoryDocumentThumbed::Bit(); } } if (caption) { diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 239bc7cf68..7806dea9f1 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -140,7 +140,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 supportChatsTimeSlice = _supportChatsTimeSlice.current(); qint32 appIncludeMutedCounter = app.includeMutedCounter() ? 1 : 0; qint32 appCountUnreadMessages = app.countUnreadMessages() ? 1 : 0; - qint32 appExeLaunchWarning = app.exeLaunchWarning() ? 1 : 0; + qint32 legacyAppExeLaunchWarning = 1; QByteArray autoDownload; qint32 supportAllSearchResults = _supportAllSearchResults.current() ? 1 : 0; qint32 archiveCollapsed = _archiveCollapsed.current() ? 1 : 0; @@ -262,7 +262,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { stream >> appCountUnreadMessages; } if (!stream.atEnd()) { - stream >> appExeLaunchWarning; + stream >> legacyAppExeLaunchWarning; } } if (!stream.atEnd()) { @@ -509,7 +509,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { } app.setIncludeMutedCounter(appIncludeMutedCounter == 1); app.setCountUnreadMessages(appCountUnreadMessages == 1); - app.setExeLaunchWarning(appExeLaunchWarning == 1); app.setNotifyAboutPinned(appNotifyAboutPinned == 1); app.setLoopAnimatedStickers(appLoopAnimatedStickers == 1); app.setLargeEmoji(appLargeEmoji == 1); diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 69fc9f2929..b742a6436c 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -1514,9 +1514,7 @@ bool Document::iconAnimated() const { } bool Document::withThumb() const { - return !songLayout() - && _data->hasThumbnail() - && !Data::IsExecutableName(_data->filename()); + return !songLayout() && _data->hasThumbnail(); } bool Document::updateStatusText() { diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index 0454578193..4849719ff3 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/fade_wrap.h" +#include "ui/widgets/fields/input_field.h" #include "ui/widgets/shadow.h" #include "ui/widgets/checkbox.h" #include "ui/vertical_list.h" @@ -114,6 +115,56 @@ void AddPremiumStar( }, badge->lifetime()); } +void OpenFileConfirmationsBox(not_null box) { + box->setTitle(tr::lng_settings_file_confirmations()); + + const auto settings = &Core::App().settings(); + const auto &list = settings->noWarningExtensions(); + const auto text = QStringList(begin(list), end(list)).join(' '); + const auto layout = box->verticalLayout(); + const auto extensions = box->addRow( + object_ptr( + box, + st::defaultInputField, + Ui::InputField::Mode::MultiLine, + tr::lng_settings_edit_extensions(), + TextWithTags{ text }), + st::boxRowPadding + QMargins(0, 0, 0, st::settingsPrivacySkip)); + Ui::AddDividerText(layout, tr::lng_settings_edit_extensions_about()); + Ui::AddSkip(layout); + const auto ip = layout->add(object_ptr( + box, + tr::lng_settings_edit_ip_confirm(), + st::settingsButtonNoIcon + ))->toggleOn(rpl::single(settings->ipRevealWarning())); + Ui::AddSkip(layout); + Ui::AddDividerText(layout, tr::lng_settings_edit_ip_confirm_about()); + + box->setFocusCallback([=] { + extensions->setFocusFast(); + }); + + box->addButton(tr::lng_settings_save(), [=] { + const auto extensionsList = extensions->getLastText() + .mid(0, 10240) + .split(' ', Qt::SkipEmptyParts) + .mid(0, 1024); + auto extensions = base::flat_set( + extensionsList.begin(), + extensionsList.end()); + const auto ipRevealWarning = ip->toggled(); + if (extensions != settings->noWarningExtensions() + || ipRevealWarning != settings->ipRevealWarning()) { + settings->setNoWarningExtensions(std::move(extensions)); + settings->setIpRevealWarning(ipRevealWarning); + Core::App().saveSettingsDelayed(); + } + box->closeBox(); + + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); +} + QString PrivacyBase(Privacy::Key key, const Privacy::Rule &rule) { using Key = Privacy::Key; using Option = Privacy::Option; @@ -645,6 +696,30 @@ void SetupBotsAndWebsites( }); Ui::AddSkip(container); + Ui::AddDivider(container); +} + +void SetupConfirmationExtensions( + not_null controller, + not_null container) { + if (Core::App().settings().noWarningExtensions().empty() + && Core::App().settings().ipRevealWarning()) { + return; + } + + Ui::AddSkip(container); + Ui::AddSubsectionTitle(container, tr::lng_settings_file_confirmations()); + + container->add(object_ptr