mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Reimplement file open confirmations.
This commit is contained in:
parent
f4a09a9ca0
commit
6a28cd1a35
17 changed files with 375 additions and 158 deletions
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -37,6 +37,8 @@ struct CloudFile final {
|
|||
|
||||
~CloudFile();
|
||||
|
||||
void clear();
|
||||
|
||||
ImageLocation location;
|
||||
std::unique_ptr<FileLoader> loader;
|
||||
int byteSize = 0;
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue