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_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";

View File

@ -90,7 +90,7 @@ QString ExtractRingtoneName(not_null<DocumentData*> 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) {

View File

@ -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<qint32>(_sendSubmitWay);
qint32 includeMutedCounter = _includeMutedCounter ? 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 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<QString>(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;

View File

@ -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<QString> & {
return _noWarningExtensions;
}
void setExeLaunchWarning(bool warning) {
_exeLaunchWarning = warning;
void setNoWarningExtensions(base::flat_set<QString> 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<QString, QString> _soundOverrides;
bool _exeLaunchWarning = true;
base::flat_set<QString> _noWarningExtensions;
bool _ipRevealWarning = true;
bool _loopAnimatedStickers = true;
rpl::variable<bool> _largeEmoji = true;

View File

@ -37,6 +37,12 @@ namespace {
&& 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
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<QMimeData> ShareMimeMediaData(
@ -194,10 +187,10 @@ std::shared_ptr<QMimeData> 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<const QMimeData*> 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<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

View File

@ -69,4 +69,21 @@ struct MimeImageData {
[[nodiscard]] QList<QUrl> ReadMimeUrls(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

View File

@ -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(

View File

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

View File

@ -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 };

View File

@ -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<DocumentData*> 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<DocumentAdditionalData> _additional;
mutable Flags _flags = kStreamingSupportedUnknown;
GoodThumbnailState _goodThumbnailState = GoodThumbnailState();
Core::NameType _nameType = Core::NameType();
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 "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(),

View File

@ -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<Ui::GenericBox*> box,
rpl::producer<TextWithEntities> &&text,
rpl::producer<QString> &&check,
rpl::producer<QString> &&confirm,
Fn<void(bool)> callback) {
auto checkbox = object_ptr<Ui::Checkbox>(
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<Ui::SlideWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_launch_dont_ask_settings(),
st::boxLabel)
))->toggleOn(weak->checkedValue());
}
void LaunchWithWarning(
// not_null<Window::Controller*> 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<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(
not_null<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,

View File

@ -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<Data::DocumentMedia*> media,
FnMut<QImage(QImage)> postprocess,

View File

@ -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) {

View File

@ -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);

View File

@ -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() {

View File

@ -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<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) {
using Key = Privacy::Key;
using Option = Privacy::Option;
@ -645,6 +696,30 @@ void SetupBotsAndWebsites(
});
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(
@ -996,8 +1071,8 @@ void PrivacySecurity::setupContent(
AddDivider(content);
#endif // !OS_MAC_STORE && !OS_WIN_STORE
SetupArchiveAndMute(controller, content);
SetupConfirmationExtensions(controller, content);
SetupBotsAndWebsites(controller, content);
AddDivider(content);
SetupSelfDestruction(controller, content, trigger());
Ui::ResizeFitChild(this, content);