1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "data/data_document_resolver.h"
9
10 #include "facades.h"
11 #include "base/platform/base_platform_info.h"
12 #include "ui/boxes/confirm_box.h"
13 #include "core/application.h"
14 #include "core/core_settings.h"
15 #include "core/mime_type.h"
16 #include "data/data_document.h"
17 #include "data/data_document_media.h"
18 #include "data/data_file_click_handler.h"
19 #include "data/data_file_origin.h"
20 #include "data/data_session.h"
21 #include "history/view/media/history_view_gif.h"
22 #include "history/history.h"
23 #include "history/history_item.h"
24 #include "media/player/media_player_instance.h"
25 #include "platform/platform_file_utilities.h"
26 #include "ui/chat/chat_theme.h"
27 #include "ui/text/text_utilities.h"
28 #include "window/window_session_controller.h"
29
30 #include <QtCore/QBuffer>
31 #include <QtCore/QMimeType>
32 #include <QtCore/QMimeDatabase>
33
34 namespace Data {
35 namespace {
36
LaunchWithWarning(const QString & name,HistoryItem * item)37 void LaunchWithWarning(
38 // not_null<Window::Controller*> controller,
39 const QString &name,
40 HistoryItem *item) {
41 const auto isExecutable = Data::IsExecutableName(name);
42 const auto isIpReveal = Data::IsIpRevealingName(name);
43 auto &app = Core::App();
44 const auto warn = [&] {
45 if (item && item->history()->peer->isVerified()) {
46 return false;
47 }
48 return (isExecutable && app.settings().exeLaunchWarning())
49 || (isIpReveal && app.settings().ipRevealWarning());
50 }();
51 const auto extension = '.' + Data::FileExtension(name);
52 if (Platform::IsWindows() && extension == u"."_q) {
53 // If you launch a file without extension, like "test", in case
54 // there is an executable file with the same name in this folder,
55 // like "test.bat", the executable file will be launched.
56 //
57 // Now we always force an Open With dialog box for such files.
58 crl::on_main([=] {
59 Platform::File::UnsafeShowOpenWith(name);
60 });
61 return;
62 } else if (!warn) {
63 File::Launch(name);
64 return;
65 }
66 const auto callback = [=, &app](bool checked) {
67 if (checked) {
68 if (isExecutable) {
69 app.settings().setExeLaunchWarning(false);
70 } else if (isIpReveal) {
71 app.settings().setIpRevealWarning(false);
72 }
73 app.saveSettingsDelayed();
74 }
75 File::Launch(name);
76 };
77 auto text = isExecutable
78 ? tr::lng_launch_exe_warning(
79 lt_extension,
80 rpl::single(Ui::Text::Bold(extension)),
81 Ui::Text::WithEntities)
82 : tr::lng_launch_svg_warning(Ui::Text::WithEntities);
83 Ui::show(Box<Ui::ConfirmDontWarnBox>(
84 std::move(text),
85 tr::lng_launch_exe_dont_ask(tr::now),
86 (isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(),
87 callback));
88 }
89
90 } // namespace
91
FileExtension(const QString & filepath)92 QString FileExtension(const QString &filepath) {
93 const auto reversed = ranges::views::reverse(filepath);
94 const auto last = ranges::find_first_of(reversed, ".\\/");
95 if (last == reversed.end() || *last != '.') {
96 return QString();
97 }
98 return QString(last.base(), last - reversed.begin());
99 }
100
101 #if 0
102 bool IsValidMediaFile(const QString &filepath) {
103 static const auto kExtensions = [] {
104 const auto list = qsl("\
105 16svx 2sf 3g2 3gp 8svx aac aaf aif aifc aiff amr amv ape asf ast au aup \
106 avchd avi brstm bwf cam cdda cust dat divx drc dsh dsf dts dtshd dtsma \
107 dvr-ms dwd evo f4a f4b f4p f4v fla flac flr flv gif gifv gsf gsm gym iff \
108 ifo it jam la ly m1v m2p m2ts m2v m4a m4p m4v mcf mid mk3d mka mks mkv mng \
109 mov mp1 mp2 mp3 mp4 minipsf mod mpc mpe mpeg mpg mpv mscz mt2 mus mxf mxl \
110 niff nsf nsv off ofr ofs ogg ogv opus ots pac ps psf psf2 psflib ptb qsf \
111 qt ra raw rka rm rmj rmvb roq s3m shn sib sid smi smp sol spc spx ssf svi \
112 swa swf tak ts tta txm usf vgm vob voc vox vqf wav webm wma wmv wrap wtv \
113 wv xm xml ym yuv").split(' ');
114 return base::flat_set<QString>(list.begin(), list.end());
115 }();
116
117 return ranges::binary_search(
118 kExtensions,
119 FileExtension(filepath).toLower());
120 }
121 #endif
122
IsExecutableName(const QString & filepath)123 bool IsExecutableName(const QString &filepath) {
124 static const auto kExtensions = [] {
125 const auto joined =
126 #ifdef Q_OS_MAC
127 qsl("\
128 applescript action app bin command csh osx workflow terminal url caction \
129 mpkg pkg scpt scptd xhtm webarchive");
130 #elif defined Q_OS_UNIX // Q_OS_MAC
131 qsl("bin csh deb desktop ksh out pet pkg pup rpm run sh shar \
132 slp zsh");
133 #else // Q_OS_MAC || Q_OS_UNIX
134 qsl("\
135 ad ade adp app application appref-ms asp asx bas bat bin cab cdxml cer cfg \
136 chi chm cmd cnt com cpl crt csh der diagcab dll drv eml exe fon fxp gadget \
137 grp hlp hpj hta htt inf ini ins inx isp isu its jar jnlp job js jse key ksh \
138 lnk local lua mad maf mag mam manifest maq mar mas mat mau mav maw mcf mda \
139 mdb mde mdt mdw mdz mht mhtml mjs mmc mof msc msg msh msh1 msh2 msh1xml \
140 msh2xml mshxml msi msp mst ops osd paf pcd phar php php3 php4 php5 php7 phps \
141 php-s pht phtml pif pl plg pm pod prf prg ps1 ps2 ps1xml ps2xml psc1 psc2 \
142 psd1 psm1 pssc pst py py3 pyc pyd pyi pyo pyw pywz pyz rb reg rgs scf scr \
143 sct search-ms settingcontent-ms sh shb shs slk sys t tmp u3p url vb vbe vbp \
144 vbs vbscript vdx vsmacros vsd vsdm vsdx vss vssm vssx vst vstm vstx vsw vsx \
145 vtx website ws wsc wsf wsh xbap xll xnk xs");
146 #endif // !Q_OS_MAC && !Q_OS_UNIX
147 const auto list = joined.split(' ');
148 return base::flat_set<QString>(list.begin(), list.end());
149 }();
150
151 return ranges::binary_search(
152 kExtensions,
153 FileExtension(filepath).toLower());
154 }
155
IsIpRevealingName(const QString & filepath)156 bool IsIpRevealingName(const QString &filepath) {
157 static const auto kExtensions = [] {
158 const auto joined = u"htm html svg"_q;
159 const auto list = joined.split(' ');
160 return base::flat_set<QString>(list.begin(), list.end());
161 }();
162 static const auto kMimeTypes = [] {
163 const auto joined = u"text/html image/svg+xml"_q;
164 const auto list = joined.split(' ');
165 return base::flat_set<QString>(list.begin(), list.end());
166 }();
167
168 return ranges::binary_search(
169 kExtensions,
170 FileExtension(filepath).toLower()
171 ) || ranges::binary_search(
172 kMimeTypes,
173 QMimeDatabase().mimeTypeForFile(QFileInfo(filepath)).name()
174 );
175 }
176
ReadBackgroundImageAsync(not_null<Data::DocumentMedia * > media,FnMut<QImage (QImage)> postprocess,FnMut<void (QImage &&)> done)177 base::binary_guard ReadBackgroundImageAsync(
178 not_null<Data::DocumentMedia*> media,
179 FnMut<QImage(QImage)> postprocess,
180 FnMut<void(QImage&&)> done) {
181 auto result = base::binary_guard();
182 const auto gzipSvg = media->owner()->isPatternWallPaperSVG();
183 crl::async([
184 gzipSvg,
185 bytes = media->bytes(),
186 path = media->owner()->filepath(),
187 postprocess = std::move(postprocess),
188 guard = result.make_guard(),
189 callback = std::move(done)
190 ]() mutable {
191 auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
192 if (postprocess) {
193 image = postprocess(std::move(image));
194 }
195 crl::on_main(std::move(guard), [
196 image = std::move(image),
197 callback = std::move(callback)
198 ]() mutable {
199 callback(std::move(image));
200 });
201 });
202 return result;
203 }
204
ResolveDocument(Window::SessionController * controller,not_null<DocumentData * > document,HistoryItem * item)205 void ResolveDocument(
206 Window::SessionController *controller,
207 not_null<DocumentData*> document,
208 HistoryItem *item) {
209 if (!document->date) {
210 return;
211 }
212 const auto msgId = item ? item->fullId() : FullMsgId();
213
214 const auto showDocument = [&] {
215 if (cUseExternalVideoPlayer()
216 && document->isVideoFile()
217 && !document->filepath().isEmpty()) {
218 File::Launch(document->location(false).fname);
219 } else if (controller) {
220 controller->openDocument(document, msgId, true);
221 }
222 };
223
224 const auto media = document->createMediaView();
225 const auto openImageInApp = [&] {
226 if (document->size >= Images::kReadBytesLimit) {
227 return false;
228 }
229 const auto &location = document->location(true);
230 if (!location.isEmpty() && location.accessEnable()) {
231 const auto guard = gsl::finally([&] {
232 location.accessDisable();
233 });
234 const auto path = location.name();
235 if (Core::MimeTypeForFile(QFileInfo(path)).name().startsWith("image/")
236 && QImageReader(path).canRead()) {
237 showDocument();
238 return true;
239 }
240 } else if (document->mimeString().startsWith("image/")
241 && !media->bytes().isEmpty()) {
242 auto bytes = media->bytes();
243 auto buffer = QBuffer(&bytes);
244 if (QImageReader(&buffer).canRead()) {
245 showDocument();
246 return true;
247 }
248 }
249 return false;
250 };
251 const auto &location = document->location(true);
252 if (document->isTheme() && media->loaded(true)) {
253 showDocument();
254 location.accessDisable();
255 } else if (media->canBePlayed()) {
256 if (document->isAudioFile()
257 || document->isVoiceMessage()
258 || document->isVideoMessage()) {
259 ::Media::Player::instance()->playPause({ document, msgId });
260 } else if (item
261 && document->isAnimation()
262 && HistoryView::Gif::CanPlayInline(document)) {
263 document->owner().requestAnimationPlayInline(item);
264 } else {
265 showDocument();
266 }
267 } else {
268 document->saveFromDataSilent();
269 if (!openImageInApp()) {
270 if (!document->filepath(true).isEmpty()) {
271 LaunchWithWarning(location.name(), item);
272 } else if (document->status == FileReady
273 || document->status == FileDownloadFailed) {
274 DocumentSaveClickHandler::Save(
275 item ? item->fullId() : Data::FileOrigin(),
276 document);
277 }
278 }
279 }
280 }
281
282 } // namespace Data
283