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