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 "media/clip/media_clip_check_streaming.h"
9 
10 #include "core/file_location.h"
11 #include "base/bytes.h"
12 #include "logs.h"
13 
14 #include <QtCore/QtEndian>
15 #include <QtCore/QBuffer>
16 #include <QtCore/QFile>
17 
18 namespace Media {
19 namespace Clip {
20 namespace {
21 
22 constexpr auto kHeaderSize = 8;
23 constexpr auto kFindMoovBefore = 128 * 1024;
24 
25 template <typename Type>
ReadBigEndian(bytes::const_span data)26 Type ReadBigEndian(bytes::const_span data) {
27 	const auto bytes = data.subspan(0, sizeof(Type)).data();
28 	return qFromBigEndian(*reinterpret_cast<const Type*>(bytes));
29 }
30 
IsAtom(bytes::const_span header,const char (& atom)[5])31 bool IsAtom(bytes::const_span header, const char (&atom)[5]) {
32 	return bytes::compare(
33 		header.subspan(4, 4),
34 		bytes::make_span(atom).subspan(0, 4)) == 0;
35 }
36 
37 } // namespace
38 
CheckStreamingSupport(const Core::FileLocation & location,QByteArray data)39 bool CheckStreamingSupport(
40 		const Core::FileLocation &location,
41 		QByteArray data) {
42 	QBuffer buffer;
43 	QFile file;
44 	if (data.isEmpty()) {
45 		file.setFileName(location.name());
46 	} else {
47 		buffer.setBuffer(&data);
48 	}
49 	const auto size = data.isEmpty()
50 		? file.size()
51 		: data.size();
52 	const auto device = data.isEmpty()
53 		? static_cast<QIODevice*>(&file)
54 		: static_cast<QIODevice*>(&buffer);
55 
56 	if (size < kHeaderSize || !device->open(QIODevice::ReadOnly)) {
57 		return false;
58 	}
59 
60 	auto lastReadPosition = 0;
61 	char atomHeader[kHeaderSize] = { 0 };
62 	auto atomHeaderBytes = bytes::make_span(atomHeader);
63 	while (true) {
64 		const auto position = device->pos();
65 		if (device->read(atomHeader, kHeaderSize) != kHeaderSize) {
66 			break;
67 		}
68 
69 		if (lastReadPosition >= kFindMoovBefore) {
70 			return false;
71 		} else if (IsAtom(atomHeaderBytes, "moov")) {
72 			return true;
73 		}
74 
75 		const auto length = [&] {
76 			const auto result = ReadBigEndian<uint32>(atomHeaderBytes);
77 			if (result != 1) {
78 				return uint64(result);
79 			}
80 			char atomSize64[kHeaderSize] = { 0 };
81 			if (device->read(atomSize64, kHeaderSize) != kHeaderSize) {
82 				return uint64(-1);
83 			}
84 			auto atomSize64Bytes = bytes::make_span(atomSize64);
85 			return ReadBigEndian<uint64>(atomSize64Bytes);
86 		}();
87 		if (position + length > size) {
88 			break;
89 		}
90 		device->seek(position + length);
91 		lastReadPosition = position;
92 	}
93 	return false;
94 }
95 
96 } // namespace Clip
97 } // namespace Media
98