File: data_document_resolver.cpp

package info (click to toggle)
telegram-desktop 4.6.5%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 53,300 kB
  • sloc: cpp: 605,857; python: 3,978; ansic: 1,636; sh: 965; makefile: 841; objc: 652; javascript: 187; xml: 165
file content (307 lines) | stat: -rw-r--r-- 9,697 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.

For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "data/data_document_resolver.h"

#include "base/platform/base_platform_info.h"
#include "ui/boxes/confirm_box.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "core/mime_type.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
#include "data/data_file_click_handler.h"
#include "data/data_file_origin.h"
#include "data/data_session.h"
#include "history/view/media/history_view_gif.h"
#include "history/history.h"
#include "history/history_item.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_file_utilities.h"
#include "ui/chat/chat_theme.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "window/window_session_controller.h"
#include "boxes/abstract_box.h" // Ui::show().
#include "styles/style_layers.h"

#include <QtCore/QBuffer>
#include <QtCore/QMimeType>
#include <QtCore/QMimeDatabase>

namespace Data {
namespace {

void ConfirmDontWarnBox(
		not_null<Ui::GenericBox*> box,
		rpl::producer<TextWithEntities> &&text,
		rpl::producer<QString> &&confirm,
		Fn<void(bool)> callback) {
	auto checkbox = object_ptr<Ui::Checkbox>(
		box.get(),
		tr::lng_launch_exe_dont_ask(),
		false,
		st::defaultBoxCheckbox);
	const auto weak = Ui::MakeWeak(checkbox.data());
	auto confirmed = crl::guard(weak, [=, callback = std::move(callback)] {
		const auto checked = weak->checked();
		box->closeBox();
		callback(checked);
	});
	Ui::ConfirmBox(box, {
		.text = std::move(text),
		.confirmed = std::move(confirmed),
		.confirmText = std::move(confirm),
	});
	auto padding = st::boxPadding;
	padding.setTop(padding.bottom());
	box->addRow(std::move(checkbox), std::move(padding));
}

void LaunchWithWarning(
		// not_null<Window::Controller*> controller,
		const QString &name,
		HistoryItem *item) {
	const auto isExecutable = Data::IsExecutableName(name);
	const auto isIpReveal = Data::IsIpRevealingName(name);
	auto &app = Core::App();
	const auto warn = [&] {
		if (item && item->history()->peer->isVerified()) {
			return false;
		}
		return (isExecutable && app.settings().exeLaunchWarning())
			|| (isIpReveal && app.settings().ipRevealWarning());
	}();
	const auto extension = '.' + Data::FileExtension(name);
	if (Platform::IsWindows() && extension == u"."_q) {
		// 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.
		crl::on_main([=] {
			Platform::File::UnsafeShowOpenWith(name);
		});
		return;
	} else if (!warn) {
		File::Launch(name);
		return;
	}
	const auto callback = [=, &app](bool checked) {
		if (checked) {
			if (isExecutable) {
				app.settings().setExeLaunchWarning(false);
			} else if (isIpReveal) {
				app.settings().setIpRevealWarning(false);
			}
			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);
	Ui::show(Box(
		ConfirmDontWarnBox,
		std::move(text),
		(isExecutable ? tr::lng_launch_exe_sure : tr::lng_continue)(),
		callback));
}

} // namespace

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_MAC
			u"\
applescript action app bin command csh osx workflow terminal url caction \
mpkg pkg scpt scptd xhtm webarchive"_q;
#elif defined Q_OS_UNIX // Q_OS_MAC
			u"bin csh deb desktop ksh out pet pkg pup rpm run sh shar \
slp zsh"_q;
#else // Q_OS_MAC || Q_OS_UNIX
			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 \
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 pywz 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 ws wsc wsf wsh xbap xll xnk xs"_q;
#endif // !Q_OS_MAC && !Q_OS_UNIX
		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,
		FnMut<void(QImage&&)> done) {
	auto result = base::binary_guard();
	const auto gzipSvg = media->owner()->isPatternWallPaperSVG();
	crl::async([
		gzipSvg,
		bytes = media->bytes(),
		path = media->owner()->filepath(),
		postprocess = std::move(postprocess),
		guard = result.make_guard(),
		callback = std::move(done)
	]() mutable {
		auto image = Ui::ReadBackgroundImage(path, bytes, gzipSvg);
		if (postprocess) {
			image = postprocess(std::move(image));
		}
		crl::on_main(std::move(guard), [
			image = std::move(image),
			callback = std::move(callback)
		]() mutable {
			callback(std::move(image));
		});
	});
	return result;
}

void ResolveDocument(
		Window::SessionController *controller,
		not_null<DocumentData*> document,
		HistoryItem *item,
		MsgId topicRootId) {
	if (document->isNull()) {
		return;
	}
	const auto msgId = item ? item->fullId() : FullMsgId();

	const auto showDocument = [&] {
		if (cUseExternalVideoPlayer()
			&& document->isVideoFile()
			&& !document->filepath().isEmpty()) {
			File::Launch(document->location(false).fname);
		} else if (controller) {
			controller->openDocument(document, msgId, topicRootId, true);
		}
	};

	const auto media = document->createMediaView();
	const auto openImageInApp = [&] {
		if (document->size >= Images::kReadBytesLimit) {
			return false;
		}
		const auto &location = document->location(true);
		if (!location.isEmpty() && location.accessEnable()) {
			const auto guard = gsl::finally([&] {
				location.accessDisable();
			});
			const auto path = location.name();
			if (Core::MimeTypeForFile(QFileInfo(path)).name().startsWith("image/")
				&& QImageReader(path).canRead()) {
				showDocument();
				return true;
			}
		} else if (document->mimeString().startsWith("image/")
			&& !media->bytes().isEmpty()) {
			auto bytes = media->bytes();
			auto buffer = QBuffer(&bytes);
			if (QImageReader(&buffer).canRead()) {
				showDocument();
				return true;
			}
		}
		return false;
	};
	const auto &location = document->location(true);
	if (document->isTheme() && media->loaded(true)) {
		showDocument();
		location.accessDisable();
	} else if (media->canBePlayed(item)) {
		if (document->isAudioFile()
			|| document->isVoiceMessage()
			|| document->isVideoMessage()) {
			::Media::Player::instance()->playPause({ document, msgId });
		} else {
			showDocument();
		}
	} else {
		document->saveFromDataSilent();
		if (!openImageInApp()) {
			if (!document->filepath(true).isEmpty()) {
				LaunchWithWarning(location.name(), item);
			} else if (document->status == FileReady
				|| document->status == FileDownloadFailed) {
				DocumentSaveClickHandler::Save(
					item ? item->fullId() : Data::FileOrigin(),
					document);
			}
		}
	}
}

} // namespace Data