Reimplement file open confirmations.

This commit is contained in:
John Preston 2024-04-15 11:18:57 +04:00
parent f4a09a9ca0
commit 6a28cd1a35
17 changed files with 375 additions and 158 deletions

View File

@ -734,6 +734,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_settings_sensitive_disable_filtering" = "Disable filtering"; "lng_settings_sensitive_disable_filtering" = "Disable filtering";
"lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices."; "lng_settings_sensitive_about" = "Display sensitive media in public channels on all your Telegram devices.";
"lng_settings_security_bots" = "Bots and websites"; "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_clear_payment_info" = "Clear Payment and Shipping Info";
"lng_settings_logged_in" = "Connected websites"; "lng_settings_logged_in" = "Connected websites";
"lng_settings_logged_in_title" = "Logged in with Telegram"; "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_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_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_svg_warning" = "Opening this file can potentially expose your IP address to its sender. Continue?";
"lng_launch_exe_sure" = "Run"; "lng_launch_exe_sure" = "Run";
"lng_launch_other_sure" = "Open";
"lng_launch_exe_dont_ask" = "Don't ask me again"; "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_anonymous" = "Anonymous Poll";
"lng_polls_public" = "Poll"; "lng_polls_public" = "Poll";

View File

@ -90,7 +90,7 @@ QString ExtractRingtoneName(not_null<DocumentData*> document) {
} }
const auto name = document->filename(); const auto name = document->filename();
if (!name.isEmpty()) { if (!name.isEmpty()) {
const auto extension = Data::FileExtension(name); const auto extension = Core::FileExtension(name);
if (extension.isEmpty()) { if (extension.isEmpty()) {
return name; return name;
} else if (name.size() > extension.size() + 1) { } else if (name.size() > extension.size() + 1) {

View File

@ -156,6 +156,10 @@ QByteArray Settings::serialize() const {
const auto &recentEmojiPreloadData = _recentEmojiPreload.empty() const auto &recentEmojiPreloadData = _recentEmojiPreload.empty()
? recentEmojiPreloadGenerated ? recentEmojiPreloadGenerated
: _recentEmojiPreload; : _recentEmojiPreload;
const auto noWarningExtensions = QStringList(
begin(_noWarningExtensions),
end(_noWarningExtensions)
).join(' ');
auto size = Serialize::bytearraySize(themesAccentColors) auto size = Serialize::bytearraySize(themesAccentColors)
+ sizeof(qint32) * 5 + sizeof(qint32) * 5
@ -212,7 +216,8 @@ QByteArray Settings::serialize() const {
+ Serialize::stringSize(_captureDeviceId.current()) + Serialize::stringSize(_captureDeviceId.current())
+ Serialize::stringSize(_callPlaybackDeviceId.current()) + Serialize::stringSize(_callPlaybackDeviceId.current())
+ Serialize::stringSize(_callCaptureDeviceId.current()) + Serialize::stringSize(_callCaptureDeviceId.current())
+ Serialize::bytearraySize(ivPosition); + Serialize::bytearraySize(ivPosition)
+ Serialize::stringSize(noWarningExtensions);
auto result = QByteArray(); auto result = QByteArray();
result.reserve(size); result.reserve(size);
@ -252,7 +257,7 @@ QByteArray Settings::serialize() const {
<< qint32(_sendSubmitWay) << qint32(_sendSubmitWay)
<< qint32(_includeMutedCounter ? 1 : 0) << qint32(_includeMutedCounter ? 1 : 0)
<< qint32(_countUnreadMessages ? 1 : 0) << qint32(_countUnreadMessages ? 1 : 0)
<< qint32(_exeLaunchWarning ? 1 : 0) << qint32(1) // legacy exe launch warning
<< qint32(_notifyAboutPinned.current() ? 1 : 0) << qint32(_notifyAboutPinned.current() ? 1 : 0)
<< qint32(_loopAnimatedStickers ? 1 : 0) << qint32(_loopAnimatedStickers ? 1 : 0)
<< qint32(_largeEmoji.current() ? 1 : 0) << qint32(_largeEmoji.current() ? 1 : 0)
@ -357,7 +362,8 @@ QByteArray Settings::serialize() const {
<< _captureDeviceId.current() << _captureDeviceId.current()
<< _callPlaybackDeviceId.current() << _callPlaybackDeviceId.current()
<< _callCaptureDeviceId.current() << _callCaptureDeviceId.current()
<< ivPosition; << ivPosition
<< noWarningExtensions;
} }
Ensures(result.size() == size); Ensures(result.size() == size);
@ -406,7 +412,8 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay); qint32 sendSubmitWay = static_cast<qint32>(_sendSubmitWay);
qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0; qint32 includeMutedCounter = _includeMutedCounter ? 1 : 0;
qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0; qint32 countUnreadMessages = _countUnreadMessages ? 1 : 0;
qint32 exeLaunchWarning = _exeLaunchWarning ? 1 : 0; std::optional<QString> noWarningExtensions;
qint32 legacyExeLaunchWarning = 1;
qint32 notifyAboutPinned = _notifyAboutPinned.current() ? 1 : 0; qint32 notifyAboutPinned = _notifyAboutPinned.current() ? 1 : 0;
qint32 loopAnimatedStickers = _loopAnimatedStickers ? 1 : 0; qint32 loopAnimatedStickers = _loopAnimatedStickers ? 1 : 0;
qint32 largeEmoji = _largeEmoji.current() ? 1 : 0; qint32 largeEmoji = _largeEmoji.current() ? 1 : 0;
@ -513,7 +520,7 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
>> sendSubmitWay >> sendSubmitWay
>> includeMutedCounter >> includeMutedCounter
>> countUnreadMessages >> countUnreadMessages
>> exeLaunchWarning >> legacyExeLaunchWarning
>> notifyAboutPinned >> notifyAboutPinned
>> loopAnimatedStickers >> loopAnimatedStickers
>> largeEmoji >> largeEmoji
@ -755,6 +762,10 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> ivPosition; stream >> ivPosition;
} }
if (!stream.atEnd()) {
noWarningExtensions = QString();
stream >> *noWarningExtensions;
}
if (stream.status() != QDataStream::Ok) { if (stream.status() != QDataStream::Ok) {
LOG(("App Error: " LOG(("App Error: "
"Bad data for Core::Settings::constructFromSerialized()")); "Bad data for Core::Settings::constructFromSerialized()"));
@ -817,7 +828,12 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
} }
_includeMutedCounter = (includeMutedCounter == 1); _includeMutedCounter = (includeMutedCounter == 1);
_countUnreadMessages = (countUnreadMessages == 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<QString>(list.begin(), list.end());
}
_ipRevealWarning = (ipRevealWarning == 1); _ipRevealWarning = (ipRevealWarning == 1);
_notifyAboutPinned = (notifyAboutPinned == 1); _notifyAboutPinned = (notifyAboutPinned == 1);
_loopAnimatedStickers = (loopAnimatedStickers == 1); _loopAnimatedStickers = (loopAnimatedStickers == 1);
@ -1283,7 +1299,7 @@ void Settings::resetOnLastLogout() {
//_sendSubmitWay = Ui::InputSubmitSettings::Enter; //_sendSubmitWay = Ui::InputSubmitSettings::Enter;
_soundOverrides = {}; _soundOverrides = {};
_exeLaunchWarning = true; _noWarningExtensions.clear();
_ipRevealWarning = true; _ipRevealWarning = true;
_loopAnimatedStickers = true; _loopAnimatedStickers = true;
_largeEmoji = true; _largeEmoji = true;

View File

@ -398,11 +398,12 @@ public:
} }
[[nodiscard]] QString getSoundPath(const QString &key) const; [[nodiscard]] QString getSoundPath(const QString &key) const;
[[nodiscard]] bool exeLaunchWarning() const { [[nodiscard]] auto noWarningExtensions() const
return _exeLaunchWarning; -> const base::flat_set<QString> & {
return _noWarningExtensions;
} }
void setExeLaunchWarning(bool warning) { void setNoWarningExtensions(base::flat_set<QString> extensions) {
_exeLaunchWarning = warning; _noWarningExtensions = std::move(extensions);
} }
[[nodiscard]] bool ipRevealWarning() const { [[nodiscard]] bool ipRevealWarning() const {
return _ipRevealWarning; return _ipRevealWarning;
@ -933,7 +934,7 @@ private:
Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay(); Ui::SendFilesWay _sendFilesWay = Ui::SendFilesWay();
Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings(); Ui::InputSubmitSettings _sendSubmitWay = Ui::InputSubmitSettings();
base::flat_map<QString, QString> _soundOverrides; base::flat_map<QString, QString> _soundOverrides;
bool _exeLaunchWarning = true; base::flat_set<QString> _noWarningExtensions;
bool _ipRevealWarning = true; bool _ipRevealWarning = true;
bool _loopAnimatedStickers = true; bool _loopAnimatedStickers = true;
rpl::variable<bool> _largeEmoji = true; rpl::variable<bool> _largeEmoji = true;

View File

@ -37,6 +37,12 @@ namespace {
&& data->hasImage(); && data->hasImage();
} }
[[nodiscard]] base::flat_set<QString> SplitExtensions(
const QString &joined) {
const auto list = joined.split(' ');
return base::flat_set<QString>(list.begin(), list.end());
}
} // namespace } // namespace
MimeType::MimeType(const QMimeType &type) : _typeStruct(type) { MimeType::MimeType(const QMimeType &type) : _typeStruct(type) {
@ -162,22 +168,9 @@ bool IsMimeAcceptedForPhotoVideoAlbum(const QString &mime) {
} }
bool FileIsImage(const QString &name, const QString &mime) { bool FileIsImage(const QString &name, const QString &mime) {
QString lowermime = mime.toLower(), namelower = name.toLower(); return name.isEmpty()
if (lowermime.startsWith(u"image/"_q)) { ? mime.toLower().startsWith(u"image/"_q)
return true; : (DetectNameType(name) == NameType::Image);
} 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;
} }
std::shared_ptr<QMimeData> ShareMimeMediaData( std::shared_ptr<QMimeData> ShareMimeMediaData(
@ -194,10 +187,10 @@ std::shared_ptr<QMimeData> ShareMimeMediaData(
result->setData(u"application/x-td-use-jpeg"_q, "1"); result->setData(u"application/x-td-use-jpeg"_q, "1");
result->setData(u"image/jpeg"_q, original->data(u"image/jpeg"_q)); 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->setUrls(std::move(list));
} }
result->setText(Core::ReadMimeText(original)); result->setText(ReadMimeText(original));
return result; return result;
} }
@ -240,4 +233,116 @@ bool CanSendFiles(not_null<const QMimeData*> data) {
return false; 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<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()
);
}
} // namespace Core } // namespace Core

View File

@ -69,4 +69,21 @@ struct MimeImageData {
[[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data); [[nodiscard]] QList<QUrl> ReadMimeUrls(not_null<const QMimeData*> data);
[[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> data); [[nodiscard]] bool CanSendFiles(not_null<const QMimeData*> 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 } // namespace Core

View File

@ -22,6 +22,14 @@ CloudFile::~CloudFile() {
base::take(loader); base::take(loader);
} }
void CloudFile::clear() {
location = {};
base::take(loader);
byteSize = 0;
progressivePartSize = 0;
flags = {};
}
CloudImage::CloudImage() = default; CloudImage::CloudImage() = default;
CloudImage::CloudImage( CloudImage::CloudImage(

View File

@ -37,6 +37,8 @@ struct CloudFile final {
~CloudFile(); ~CloudFile();
void clear();
ImageLocation location; ImageLocation location;
std::unique_ptr<FileLoader> loader; std::unique_ptr<FileLoader> loader;
int byteSize = 0; int byteSize = 0;

View File

@ -478,6 +478,31 @@ void DocumentData::setattributes(
_additional = nullptr; _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() if (isAudioFile()
|| isAnimation() || isAnimation()
|| isVoiceMessage() || isVoiceMessage()
@ -530,6 +555,10 @@ void DocumentData::updateThumbnails(
const ImageWithLocation &thumbnail, const ImageWithLocation &thumbnail,
const ImageWithLocation &videoThumbnail, const ImageWithLocation &videoThumbnail,
bool isPremiumSticker) { bool isPremiumSticker) {
if (!_filename.isEmpty()
&& !Core::NameTypeAllowsThumbnail(Core::DetectNameType(_filename))) {
return;
}
if (!inlineThumbnail.bytes.isEmpty() if (!inlineThumbnail.bytes.isEmpty()
&& _inlineThumbnailBytes.isEmpty()) { && _inlineThumbnailBytes.isEmpty()) {
_inlineThumbnailBytes = inlineThumbnail.bytes; _inlineThumbnailBytes = inlineThumbnail.bytes;
@ -919,6 +948,7 @@ void DocumentData::setFileName(const QString &remoteFileName) {
for (const auto &ch : controls) { for (const auto &ch : controls) {
_filename = std::move(_filename).replace(ch, "_"); _filename = std::move(_filename).replace(ch, "_");
} }
_nameType = Core::DetectNameType(_filename);
} }
void DocumentData::setLoadedInMediaCacheLocation() { void DocumentData::setLoadedInMediaCacheLocation() {
@ -1460,6 +1490,10 @@ QString DocumentData::filename() const {
return _filename; return _filename;
} }
Core::NameType DocumentData::nameType() const {
return _nameType;
}
QString DocumentData::mimeString() const { QString DocumentData::mimeString() const {
return _mimeString; return _mimeString;
} }
@ -1527,7 +1561,10 @@ bool DocumentData::isVideoMessage() const {
bool DocumentData::isAnimation() const { bool DocumentData::isAnimation() const {
return (type == AnimatedDocument) return (type == AnimatedDocument)
|| isVideoMessage() || isVideoMessage()
|| (hasMimeType(u"image/gif"_q) || ((_filename.isEmpty()
|| _nameType == Core::NameType::Image
|| _nameType == Core::NameType::Video)
&& hasMimeType(u"image/gif"_q)
&& !(_flags & Flag::StreamingPlaybackFailed)); && !(_flags & Flag::StreamingPlaybackFailed));
} }
@ -1537,9 +1574,11 @@ bool DocumentData::isGifv() const {
} }
bool DocumentData::isTheme() const { bool DocumentData::isTheme() const {
return hasMimeType(u"application/x-tgtheme-tdesktop"_q) return _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive)
|| _filename.endsWith(u".tdesktop-theme"_q, Qt::CaseInsensitive) || _filename.endsWith(u".tdesktop-palette"_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 { bool DocumentData::isSong() const {
@ -1562,6 +1601,10 @@ bool DocumentData::isAudioFile() const {
return true; return true;
} }
return false; 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 left = _mimeString.mid(prefix.size());
const auto types = { u"x-wav"_q, u"wav"_q, u"mp4"_q }; const auto types = { u"x-wav"_q, u"wav"_q, u"mp4"_q };

View File

@ -20,6 +20,10 @@ namespace Images {
class Source; class Source;
} // namespace Images } // namespace Images
namespace Core {
enum class NameType : uchar;
} // namespace Core
namespace Storage { namespace Storage {
namespace Cache { namespace Cache {
struct Key; struct Key;
@ -255,6 +259,7 @@ public:
void collectLocalData(not_null<DocumentData*> local); void collectLocalData(not_null<DocumentData*> local);
[[nodiscard]] QString filename() const; [[nodiscard]] QString filename() const;
[[nodiscard]] Core::NameType nameType() const;
[[nodiscard]] QString mimeString() const; [[nodiscard]] QString mimeString() const;
[[nodiscard]] bool hasMimeType(const QString &mime) const; [[nodiscard]] bool hasMimeType(const QString &mime) const;
void setMimeString(const QString &mime); void setMimeString(const QString &mime);
@ -369,6 +374,7 @@ private:
std::unique_ptr<DocumentAdditionalData> _additional; std::unique_ptr<DocumentAdditionalData> _additional;
mutable Flags _flags = kStreamingSupportedUnknown; mutable Flags _flags = kStreamingSupportedUnknown;
GoodThumbnailState _goodThumbnailState = GoodThumbnailState(); GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
Core::NameType _nameType = Core::NameType();
std::unique_ptr<FileLoader> _loader; std::unique_ptr<FileLoader> _loader;
}; };

View File

@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "window/themes/window_theme_preview.h" #include "window/themes/window_theme_preview.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "core/application.h" #include "core/application.h"
#include "core/mime_type.h"
#include "storage/file_download.h" #include "storage/file_download.h"
#include "ui/chat/attach/attach_prepare.h" #include "ui/chat/attach/attach_prepare.h"
@ -295,10 +296,12 @@ void DocumentMedia::automaticLoad(
// No automatic download in this case. // No automatic download in this case.
return; return;
} }
const auto indata = _owner->filename();
const auto filename = toCache const auto filename = toCache
? QString() ? QString()
: DocumentFileNameForSave(_owner); : DocumentFileNameForSave(_owner);
const auto shouldLoadFromCloud = !Data::IsExecutableName(filename) const auto shouldLoadFromCloud = (indata.isEmpty()
|| Core::DetectNameType(indata) != Core::NameType::Executable)
&& (item && (item
? Data::AutoDownload::Should( ? Data::AutoDownload::Should(
_owner->session().settings().autoDownload(), _owner->session().settings().autoDownload(),

View File

@ -28,6 +28,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/chat/chat_theme.h" #include "ui/chat/chat_theme.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/wrap/slide_wrap.h"
#include "window/window_session_controller.h" #include "window/window_session_controller.h"
#include "styles/style_layers.h" #include "styles/style_layers.h"
@ -46,11 +47,12 @@ base::options::toggle OptionExternalVideoPlayer({
void ConfirmDontWarnBox( void ConfirmDontWarnBox(
not_null<Ui::GenericBox*> box, not_null<Ui::GenericBox*> box,
rpl::producer<TextWithEntities> &&text, rpl::producer<TextWithEntities> &&text,
rpl::producer<QString> &&check,
rpl::producer<QString> &&confirm, rpl::producer<QString> &&confirm,
Fn<void(bool)> callback) { Fn<void(bool)> callback) {
auto checkbox = object_ptr<Ui::Checkbox>( auto checkbox = object_ptr<Ui::Checkbox>(
box.get(), box.get(),
tr::lng_launch_exe_dont_ask(), std::move(check),
false, false,
st::defaultBoxCheckbox); st::defaultBoxCheckbox);
const auto weak = Ui::MakeWeak(checkbox.data()); const auto weak = Ui::MakeWeak(checkbox.data());
@ -67,29 +69,43 @@ void ConfirmDontWarnBox(
auto padding = st::boxPadding; auto padding = st::boxPadding;
padding.setTop(padding.bottom()); padding.setTop(padding.bottom());
box->addRow(std::move(checkbox), std::move(padding)); box->addRow(std::move(checkbox), std::move(padding));
box->addRow(object_ptr<Ui::SlideWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_launch_dont_ask_settings(),
st::boxLabel)
))->toggleOn(weak->checkedValue());
} }
void LaunchWithWarning( void LaunchWithWarning(
// not_null<Window::Controller*> controller, // not_null<Window::Controller*> controller,
const QString &name, const QString &name,
HistoryItem *item) { HistoryItem *item) {
const auto isExecutable = Data::IsExecutableName(name); const auto nameType = Core::DetectNameType(name);
const auto isIpReveal = Data::IsIpRevealingName(name); const auto isIpReveal = (nameType != Core::NameType::Executable)
&& Core::IsIpRevealingPath(name);
const auto extension = Core::FileExtension(name).toLower();
auto &app = Core::App(); auto &app = Core::App();
auto &settings = app.settings();
const auto warn = [&] { const auto warn = [&] {
if (item && item->history()->peer->isVerified()) { if (item && item->history()->peer->isVerified()) {
return false; return false;
} }
return (isExecutable && app.settings().exeLaunchWarning()) return (isIpReveal && settings.ipRevealWarning())
|| (isIpReveal && app.settings().ipRevealWarning()); || ((nameType == Core::NameType::Executable
|| nameType == Core::NameType::Unknown)
&& !settings.noWarningExtensions().contains(extension));
}(); }();
const auto extension = '.' + Data::FileExtension(name); if (extension.isEmpty()) {
if (Platform::IsWindows() && extension == u"."_q) {
// If you launch a file without extension, like "test", in case // If you launch a file without extension, like "test", in case
// there is an executable file with the same name in this folder, // there is an executable file with the same name in this folder,
// like "test.bat", the executable file will be launched. // like "test.bat", the executable file will be launched.
// //
// Now we always force an Open With dialog box for such files. // 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([=] { crl::on_main([=] {
Platform::File::UnsafeShowOpenWith(name); Platform::File::UnsafeShowOpenWith(name);
}); });
@ -98,27 +114,38 @@ void LaunchWithWarning(
File::Launch(name); File::Launch(name);
return; return;
} }
const auto callback = [=, &app](bool checked) { const auto callback = [=, &app, &settings](bool checked) {
if (checked) { if (checked) {
if (isExecutable) { if (isIpReveal) {
app.settings().setExeLaunchWarning(false); settings.setIpRevealWarning(false);
} else if (isIpReveal) { } else {
app.settings().setIpRevealWarning(false); auto copy = settings.noWarningExtensions();
copy.emplace(extension);
settings.setNoWarningExtensions(std::move(copy));
} }
app.saveSettingsDelayed(); app.saveSettingsDelayed();
} }
File::Launch(name); File::Launch(name);
}; };
auto text = isExecutable auto text = isIpReveal
? tr::lng_launch_exe_warning( ? tr::lng_launch_svg_warning(Ui::Text::WithEntities)
lt_extension, : ((nameType == Core::NameType::Executable)
rpl::single(Ui::Text::Bold(extension)), ? tr::lng_launch_exe_warning
Ui::Text::WithEntities) : tr::lng_launch_other_warning)(
: tr::lng_launch_svg_warning(Ui::Text::WithEntities); 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( Ui::show(Box(
ConfirmDontWarnBox, ConfirmDontWarnBox,
std::move(text), std::move(text),
(isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(), std::move(check),
std::move(confirm),
callback)); callback));
} }
@ -126,91 +153,6 @@ void LaunchWithWarning(
const char kOptionExternalVideoPlayer[] = "external-video-player"; 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<QString>(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<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 m4v m3u8"_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 ReadBackgroundImageAsync( base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media, not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess, FnMut<QImage(QImage)> postprocess,

View File

@ -22,10 +22,6 @@ class DocumentMedia;
extern const char kOptionExternalVideoPlayer[]; 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( base::binary_guard ReadBackgroundImageAsync(
not_null<Data::DocumentMedia*> media, not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess, FnMut<QImage(QImage)> postprocess,

View File

@ -380,12 +380,9 @@ void Document::createComponents(bool caption) {
mask |= HistoryDocumentVoice::Bit(); mask |= HistoryDocumentVoice::Bit();
} else { } else {
mask |= HistoryDocumentNamed::Bit(); mask |= HistoryDocumentNamed::Bit();
if (_data->hasThumbnail()) { if (_data->hasThumbnail() && !_data->isSong()) {
if (!_data->isSong() _data->loadThumbnail(_realParent->fullId());
&& !Data::IsExecutableName(_data->filename())) { mask |= HistoryDocumentThumbed::Bit();
_data->loadThumbnail(_realParent->fullId());
mask |= HistoryDocumentThumbed::Bit();
}
} }
} }
if (caption) { if (caption) {

View File

@ -140,7 +140,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
qint32 supportChatsTimeSlice = _supportChatsTimeSlice.current(); qint32 supportChatsTimeSlice = _supportChatsTimeSlice.current();
qint32 appIncludeMutedCounter = app.includeMutedCounter() ? 1 : 0; qint32 appIncludeMutedCounter = app.includeMutedCounter() ? 1 : 0;
qint32 appCountUnreadMessages = app.countUnreadMessages() ? 1 : 0; qint32 appCountUnreadMessages = app.countUnreadMessages() ? 1 : 0;
qint32 appExeLaunchWarning = app.exeLaunchWarning() ? 1 : 0; qint32 legacyAppExeLaunchWarning = 1;
QByteArray autoDownload; QByteArray autoDownload;
qint32 supportAllSearchResults = _supportAllSearchResults.current() ? 1 : 0; qint32 supportAllSearchResults = _supportAllSearchResults.current() ? 1 : 0;
qint32 archiveCollapsed = _archiveCollapsed.current() ? 1 : 0; qint32 archiveCollapsed = _archiveCollapsed.current() ? 1 : 0;
@ -262,7 +262,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
stream >> appCountUnreadMessages; stream >> appCountUnreadMessages;
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
stream >> appExeLaunchWarning; stream >> legacyAppExeLaunchWarning;
} }
} }
if (!stream.atEnd()) { if (!stream.atEnd()) {
@ -509,7 +509,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
} }
app.setIncludeMutedCounter(appIncludeMutedCounter == 1); app.setIncludeMutedCounter(appIncludeMutedCounter == 1);
app.setCountUnreadMessages(appCountUnreadMessages == 1); app.setCountUnreadMessages(appCountUnreadMessages == 1);
app.setExeLaunchWarning(appExeLaunchWarning == 1);
app.setNotifyAboutPinned(appNotifyAboutPinned == 1); app.setNotifyAboutPinned(appNotifyAboutPinned == 1);
app.setLoopAnimatedStickers(appLoopAnimatedStickers == 1); app.setLoopAnimatedStickers(appLoopAnimatedStickers == 1);
app.setLargeEmoji(appLargeEmoji == 1); app.setLargeEmoji(appLargeEmoji == 1);

View File

@ -1514,9 +1514,7 @@ bool Document::iconAnimated() const {
} }
bool Document::withThumb() const { bool Document::withThumb() const {
return !songLayout() return !songLayout() && _data->hasThumbnail();
&& _data->hasThumbnail()
&& !Data::IsExecutableName(_data->filename());
} }
bool Document::updateStatusText() { bool Document::updateStatusText() {

View File

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/toast/toast.h" #include "ui/toast/toast.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/shadow.h" #include "ui/widgets/shadow.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
@ -114,6 +115,56 @@ void AddPremiumStar(
}, badge->lifetime()); }, badge->lifetime());
} }
void OpenFileConfirmationsBox(not_null<Ui::GenericBox*> 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<Ui::InputField>(
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<Ui::SettingsButton>(
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<QString>(
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) { QString PrivacyBase(Privacy::Key key, const Privacy::Rule &rule) {
using Key = Privacy::Key; using Key = Privacy::Key;
using Option = Privacy::Option; using Option = Privacy::Option;
@ -645,6 +696,30 @@ void SetupBotsAndWebsites(
}); });
Ui::AddSkip(container); Ui::AddSkip(container);
Ui::AddDivider(container);
}
void SetupConfirmationExtensions(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> 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<Button>(
container,
tr::lng_settings_edit_extensions(),
st::settingsButtonNoIcon
))->addClickHandler([=] {
controller->show(Box(OpenFileConfirmationsBox));
});
Ui::AddSkip(container);
Ui::AddDividerText(container, tr::lng_settings_edit_extensions_about());
} }
void SetupBlockedList( void SetupBlockedList(
@ -996,8 +1071,8 @@ void PrivacySecurity::setupContent(
AddDivider(content); AddDivider(content);
#endif // !OS_MAC_STORE && !OS_WIN_STORE #endif // !OS_MAC_STORE && !OS_WIN_STORE
SetupArchiveAndMute(controller, content); SetupArchiveAndMute(controller, content);
SetupConfirmationExtensions(controller, content);
SetupBotsAndWebsites(controller, content); SetupBotsAndWebsites(controller, content);
AddDivider(content);
SetupSelfDestruction(controller, content, trigger()); SetupSelfDestruction(controller, content, trigger());
Ui::ResizeFitChild(this, content); Ui::ResizeFitChild(this, content);