1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "td/telegram/Photo.h"
8
9 #include "td/telegram/files/FileEncryptionKey.h"
10 #include "td/telegram/files/FileLocation.h"
11 #include "td/telegram/files/FileManager.h"
12 #include "td/telegram/net/DcId.h"
13
14 #include "td/utils/algorithm.h"
15 #include "td/utils/base64.h"
16 #include "td/utils/common.h"
17 #include "td/utils/format.h"
18 #include "td/utils/HttpUrl.h"
19 #include "td/utils/logging.h"
20 #include "td/utils/misc.h"
21 #include "td/utils/Random.h"
22 #include "td/utils/SliceBuilder.h"
23
24 #include <algorithm>
25 #include <cmath>
26 #include <limits>
27
28 namespace td {
29
get_dimension(int32 size,const char * source)30 static uint16 get_dimension(int32 size, const char *source) {
31 if (size < 0 || size > 65535) {
32 LOG(ERROR) << "Wrong image dimension = " << size << " from " << source;
33 return 0;
34 }
35 return narrow_cast<uint16>(size);
36 }
37
get_dimensions(int32 width,int32 height,const char * source)38 Dimensions get_dimensions(int32 width, int32 height, const char *source) {
39 Dimensions result;
40 result.width = get_dimension(width, source);
41 result.height = get_dimension(height, source);
42 if (result.width == 0 || result.height == 0) {
43 result.width = 0;
44 result.height = 0;
45 }
46 return result;
47 }
48
get_pixel_count(const Dimensions & dimensions)49 static uint32 get_pixel_count(const Dimensions &dimensions) {
50 return static_cast<uint32>(dimensions.width) * static_cast<uint32>(dimensions.height);
51 }
52
operator ==(const Dimensions & lhs,const Dimensions & rhs)53 bool operator==(const Dimensions &lhs, const Dimensions &rhs) {
54 return lhs.width == rhs.width && lhs.height == rhs.height;
55 }
56
operator !=(const Dimensions & lhs,const Dimensions & rhs)57 bool operator!=(const Dimensions &lhs, const Dimensions &rhs) {
58 return !(lhs == rhs);
59 }
60
operator <<(StringBuilder & string_builder,const Dimensions & dimensions)61 StringBuilder &operator<<(StringBuilder &string_builder, const Dimensions &dimensions) {
62 return string_builder << "(" << dimensions.width << ", " << dimensions.height << ")";
63 }
64
get_minithumbnail_object(const string & packed)65 td_api::object_ptr<td_api::minithumbnail> get_minithumbnail_object(const string &packed) {
66 if (packed.size() < 3) {
67 return nullptr;
68 }
69 if (packed[0] == '\x01') {
70 static const string header =
71 base64_decode(
72 "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////"
73 "m8H///"
74 "/6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/"
75 "wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/"
76 "8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0R"
77 "FRkd"
78 "ISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2"
79 "uHi4"
80 "+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/"
81 "8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkN"
82 "ERUZ"
83 "HSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2"
84 "Nna4"
85 "uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=")
86 .move_as_ok();
87 static const string footer = base64_decode("/9k=").move_as_ok();
88 auto result = td_api::make_object<td_api::minithumbnail>();
89 result->height_ = static_cast<unsigned char>(packed[1]);
90 result->width_ = static_cast<unsigned char>(packed[2]);
91 result->data_ = PSTRING() << header.substr(0, 164) << packed[1] << header[165] << packed[2] << header.substr(167)
92 << packed.substr(3) << footer;
93 return result;
94 }
95 return nullptr;
96 }
97
get_thumbnail_format_object(PhotoFormat format)98 static td_api::object_ptr<td_api::ThumbnailFormat> get_thumbnail_format_object(PhotoFormat format) {
99 switch (format) {
100 case PhotoFormat::Jpeg:
101 return td_api::make_object<td_api::thumbnailFormatJpeg>();
102 case PhotoFormat::Png:
103 return td_api::make_object<td_api::thumbnailFormatPng>();
104 case PhotoFormat::Webp:
105 return td_api::make_object<td_api::thumbnailFormatWebp>();
106 case PhotoFormat::Gif:
107 return td_api::make_object<td_api::thumbnailFormatGif>();
108 case PhotoFormat::Tgs:
109 return td_api::make_object<td_api::thumbnailFormatTgs>();
110 case PhotoFormat::Mpeg4:
111 return td_api::make_object<td_api::thumbnailFormatMpeg4>();
112 default:
113 UNREACHABLE();
114 return nullptr;
115 }
116 }
117
operator <<(StringBuilder & string_builder,PhotoFormat format)118 static StringBuilder &operator<<(StringBuilder &string_builder, PhotoFormat format) {
119 switch (format) {
120 case PhotoFormat::Jpeg:
121 return string_builder << "jpg";
122 case PhotoFormat::Png:
123 return string_builder << "png";
124 case PhotoFormat::Webp:
125 return string_builder << "webp";
126 case PhotoFormat::Gif:
127 return string_builder << "gif";
128 case PhotoFormat::Tgs:
129 return string_builder << "tgs";
130 case PhotoFormat::Mpeg4:
131 return string_builder << "mp4";
132 default:
133 UNREACHABLE();
134 return string_builder;
135 }
136 }
137
register_photo(FileManager * file_manager,const PhotoSizeSource & source,int64 id,int64 access_hash,std::string file_reference,DialogId owner_dialog_id,int32 file_size,DcId dc_id,PhotoFormat format)138 static FileId register_photo(FileManager *file_manager, const PhotoSizeSource &source, int64 id, int64 access_hash,
139 std::string file_reference, DialogId owner_dialog_id, int32 file_size, DcId dc_id,
140 PhotoFormat format) {
141 LOG(DEBUG) << "Receive " << format << " photo " << id << " of type " << source.get_file_type("register_photo")
142 << " from " << dc_id;
143 auto suggested_name = PSTRING() << source.get_unique_name(id) << '.' << format;
144 auto file_location_source = owner_dialog_id.get_type() == DialogType::SecretChat ? FileLocationSource::FromUser
145 : FileLocationSource::FromServer;
146 return file_manager->register_remote(
147 FullRemoteFileLocation(source, id, access_hash, dc_id, std::move(file_reference)), file_location_source,
148 owner_dialog_id, file_size, 0, std::move(suggested_name));
149 }
150
get_profile_photo(FileManager * file_manager,UserId user_id,int64 user_access_hash,tl_object_ptr<telegram_api::UserProfilePhoto> && profile_photo_ptr)151 ProfilePhoto get_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash,
152 tl_object_ptr<telegram_api::UserProfilePhoto> &&profile_photo_ptr) {
153 ProfilePhoto result;
154 int32 profile_photo_id =
155 profile_photo_ptr == nullptr ? telegram_api::userProfilePhotoEmpty::ID : profile_photo_ptr->get_id();
156 switch (profile_photo_id) {
157 case telegram_api::userProfilePhotoEmpty::ID:
158 break;
159 case telegram_api::userProfilePhoto::ID: {
160 auto profile_photo = move_tl_object_as<telegram_api::userProfilePhoto>(profile_photo_ptr);
161
162 auto dc_id = DcId::create(profile_photo->dc_id_);
163 result.has_animation = profile_photo->has_video_;
164 result.id = profile_photo->photo_id_;
165 result.minithumbnail = profile_photo->stripped_thumb_.as_slice().str();
166 result.small_file_id = register_photo(
167 file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, false), result.id,
168 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
169 result.big_file_id = register_photo(
170 file_manager, PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, true), result.id,
171 0 /*access_hash*/, "" /*file_reference*/, DialogId(), 0 /*file_size*/, dc_id, PhotoFormat::Jpeg);
172 break;
173 }
174 default:
175 UNREACHABLE();
176 break;
177 }
178
179 return result;
180 }
181
get_profile_photo_object(FileManager * file_manager,const ProfilePhoto & profile_photo)182 tl_object_ptr<td_api::profilePhoto> get_profile_photo_object(FileManager *file_manager,
183 const ProfilePhoto &profile_photo) {
184 if (!profile_photo.small_file_id.is_valid()) {
185 return nullptr;
186 }
187 return td_api::make_object<td_api::profilePhoto>(
188 profile_photo.id, file_manager->get_file_object(profile_photo.small_file_id),
189 file_manager->get_file_object(profile_photo.big_file_id), get_minithumbnail_object(profile_photo.minithumbnail),
190 profile_photo.has_animation);
191 }
192
operator ==(const ProfilePhoto & lhs,const ProfilePhoto & rhs)193 bool operator==(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
194 bool location_differs = lhs.small_file_id != rhs.small_file_id || lhs.big_file_id != rhs.big_file_id;
195 bool id_differs = lhs.id != rhs.id;
196
197 if (location_differs) {
198 return false;
199 }
200 return lhs.has_animation == rhs.has_animation && lhs.minithumbnail == rhs.minithumbnail && !id_differs;
201 }
202
operator !=(const ProfilePhoto & lhs,const ProfilePhoto & rhs)203 bool operator!=(const ProfilePhoto &lhs, const ProfilePhoto &rhs) {
204 return !(lhs == rhs);
205 }
206
operator <<(StringBuilder & string_builder,const ProfilePhoto & profile_photo)207 StringBuilder &operator<<(StringBuilder &string_builder, const ProfilePhoto &profile_photo) {
208 return string_builder << "<ID = " << profile_photo.id << ", small_file_id = " << profile_photo.small_file_id
209 << ", big_file_id = " << profile_photo.big_file_id
210 << ", has_animation = " << profile_photo.has_animation << ">";
211 }
212
get_dialog_photo(FileManager * file_manager,DialogId dialog_id,int64 dialog_access_hash,tl_object_ptr<telegram_api::ChatPhoto> && chat_photo_ptr)213 DialogPhoto get_dialog_photo(FileManager *file_manager, DialogId dialog_id, int64 dialog_access_hash,
214 tl_object_ptr<telegram_api::ChatPhoto> &&chat_photo_ptr) {
215 int32 chat_photo_id = chat_photo_ptr == nullptr ? telegram_api::chatPhotoEmpty::ID : chat_photo_ptr->get_id();
216
217 DialogPhoto result;
218 switch (chat_photo_id) {
219 case telegram_api::chatPhotoEmpty::ID:
220 break;
221 case telegram_api::chatPhoto::ID: {
222 auto chat_photo = move_tl_object_as<telegram_api::chatPhoto>(chat_photo_ptr);
223
224 auto dc_id = DcId::create(chat_photo->dc_id_);
225 result.has_animation = chat_photo->has_video_;
226 result.minithumbnail = chat_photo->stripped_thumb_.as_slice().str();
227 result.small_file_id =
228 register_photo(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, false),
229 chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
230 result.big_file_id =
231 register_photo(file_manager, PhotoSizeSource::dialog_photo(dialog_id, dialog_access_hash, true),
232 chat_photo->photo_id_, 0, "", DialogId(), 0, dc_id, PhotoFormat::Jpeg);
233
234 break;
235 }
236 default:
237 UNREACHABLE();
238 break;
239 }
240
241 return result;
242 }
243
get_chat_photo_info_object(FileManager * file_manager,const DialogPhoto * dialog_photo)244 tl_object_ptr<td_api::chatPhotoInfo> get_chat_photo_info_object(FileManager *file_manager,
245 const DialogPhoto *dialog_photo) {
246 if (dialog_photo == nullptr || !dialog_photo->small_file_id.is_valid()) {
247 return nullptr;
248 }
249 return td_api::make_object<td_api::chatPhotoInfo>(file_manager->get_file_object(dialog_photo->small_file_id),
250 file_manager->get_file_object(dialog_photo->big_file_id),
251 get_minithumbnail_object(dialog_photo->minithumbnail),
252 dialog_photo->has_animation);
253 }
254
dialog_photo_get_file_ids(const DialogPhoto & dialog_photo)255 vector<FileId> dialog_photo_get_file_ids(const DialogPhoto &dialog_photo) {
256 vector<FileId> result;
257 if (dialog_photo.small_file_id.is_valid()) {
258 result.push_back(dialog_photo.small_file_id);
259 }
260 if (dialog_photo.big_file_id.is_valid()) {
261 result.push_back(dialog_photo.big_file_id);
262 }
263 return result;
264 }
265
as_fake_dialog_photo(const Photo & photo,DialogId dialog_id)266 DialogPhoto as_fake_dialog_photo(const Photo &photo, DialogId dialog_id) {
267 DialogPhoto result;
268 if (!photo.is_empty()) {
269 for (auto &size : photo.photos) {
270 if (size.type == 'a') {
271 result.small_file_id = size.file_id;
272 } else if (size.type == 'c') {
273 result.big_file_id = size.file_id;
274 }
275 }
276 result.minithumbnail = photo.minithumbnail;
277 result.has_animation = !photo.animations.empty();
278 if (!result.small_file_id.is_valid() || !result.big_file_id.is_valid()) {
279 LOG(ERROR) << "Failed to convert " << photo << " to chat photo of " << dialog_id;
280 return DialogPhoto();
281 }
282 }
283 return result;
284 }
285
as_profile_photo(FileManager * file_manager,UserId user_id,int64 user_access_hash,const Photo & photo)286 ProfilePhoto as_profile_photo(FileManager *file_manager, UserId user_id, int64 user_access_hash, const Photo &photo) {
287 ProfilePhoto result;
288 static_cast<DialogPhoto &>(result) = as_fake_dialog_photo(photo, DialogId(user_id));
289 if (!result.small_file_id.is_valid()) {
290 return result;
291 }
292
293 auto reregister_photo = [&](bool is_big, FileId file_id) {
294 auto file_view = file_manager->get_file_view(file_id);
295 CHECK(file_view.has_remote_location());
296 auto remote = file_view.remote_location();
297 CHECK(remote.is_photo());
298 CHECK(!remote.is_web());
299 remote.set_source(PhotoSizeSource::dialog_photo(DialogId(user_id), user_access_hash, is_big));
300 return file_manager->register_remote(std::move(remote), FileLocationSource::FromServer, DialogId(),
301 file_view.size(), file_view.expected_size(), file_view.remote_name());
302 };
303
304 result.id = photo.id.get();
305 result.small_file_id = reregister_photo(false, result.small_file_id);
306 result.big_file_id = reregister_photo(true, result.big_file_id);
307
308 return result;
309 }
310
operator ==(const DialogPhoto & lhs,const DialogPhoto & rhs)311 bool operator==(const DialogPhoto &lhs, const DialogPhoto &rhs) {
312 return lhs.small_file_id == rhs.small_file_id && lhs.big_file_id == rhs.big_file_id &&
313 lhs.minithumbnail == rhs.minithumbnail && lhs.has_animation == rhs.has_animation;
314 }
315
operator !=(const DialogPhoto & lhs,const DialogPhoto & rhs)316 bool operator!=(const DialogPhoto &lhs, const DialogPhoto &rhs) {
317 return !(lhs == rhs);
318 }
319
operator <<(StringBuilder & string_builder,const DialogPhoto & dialog_photo)320 StringBuilder &operator<<(StringBuilder &string_builder, const DialogPhoto &dialog_photo) {
321 return string_builder << "<small_file_id = " << dialog_photo.small_file_id
322 << ", big_file_id = " << dialog_photo.big_file_id
323 << ", has_animation = " << dialog_photo.has_animation << ">";
324 }
325
get_secret_thumbnail_photo_size(FileManager * file_manager,BufferSlice bytes,DialogId owner_dialog_id,int32 width,int32 height)326 PhotoSize get_secret_thumbnail_photo_size(FileManager *file_manager, BufferSlice bytes, DialogId owner_dialog_id,
327 int32 width, int32 height) {
328 if (bytes.empty()) {
329 return PhotoSize();
330 }
331 PhotoSize res;
332 res.type = 't';
333 res.dimensions = get_dimensions(width, height, "get_secret_thumbnail_photo_size");
334 res.size = narrow_cast<int32>(bytes.size());
335
336 // generate some random remote location to save
337 auto dc_id = DcId::invalid();
338 auto photo_id = -(Random::secure_int64() & std::numeric_limits<int64>::max());
339
340 res.file_id = file_manager->register_remote(
341 FullRemoteFileLocation(PhotoSizeSource::thumbnail(FileType::EncryptedThumbnail, 't'), photo_id, 0, dc_id,
342 string()),
343 FileLocationSource::FromServer, owner_dialog_id, res.size, 0,
344 PSTRING() << static_cast<uint64>(photo_id) << ".jpg");
345 file_manager->set_content(res.file_id, std::move(bytes));
346
347 return res;
348 }
349
get_photo_size(FileManager * file_manager,PhotoSizeSource source,int64 id,int64 access_hash,std::string file_reference,DcId dc_id,DialogId owner_dialog_id,tl_object_ptr<telegram_api::PhotoSize> && size_ptr,PhotoFormat format)350 Variant<PhotoSize, string> get_photo_size(FileManager *file_manager, PhotoSizeSource source, int64 id,
351 int64 access_hash, std::string file_reference, DcId dc_id,
352 DialogId owner_dialog_id, tl_object_ptr<telegram_api::PhotoSize> &&size_ptr,
353 PhotoFormat format) {
354 CHECK(size_ptr != nullptr);
355
356 string type;
357 PhotoSize res;
358 BufferSlice content;
359 switch (size_ptr->get_id()) {
360 case telegram_api::photoSizeEmpty::ID:
361 return std::move(res);
362 case telegram_api::photoSize::ID: {
363 auto size = move_tl_object_as<telegram_api::photoSize>(size_ptr);
364
365 type = std::move(size->type_);
366 res.dimensions = get_dimensions(size->w_, size->h_, "photoSize");
367 res.size = size->size_;
368
369 break;
370 }
371 case telegram_api::photoCachedSize::ID: {
372 auto size = move_tl_object_as<telegram_api::photoCachedSize>(size_ptr);
373
374 type = std::move(size->type_);
375 CHECK(size->bytes_.size() <= static_cast<size_t>(std::numeric_limits<int32>::max()));
376 res.dimensions = get_dimensions(size->w_, size->h_, "photoCachedSize");
377 res.size = static_cast<int32>(size->bytes_.size());
378
379 content = std::move(size->bytes_);
380
381 break;
382 }
383 case telegram_api::photoStrippedSize::ID: {
384 auto size = move_tl_object_as<telegram_api::photoStrippedSize>(size_ptr);
385 if (format != PhotoFormat::Jpeg) {
386 LOG(ERROR) << "Receive unexpected JPEG minithumbnail in photo " << id << " from " << source << " of format "
387 << format;
388 return std::move(res);
389 }
390 return size->bytes_.as_slice().str();
391 }
392 case telegram_api::photoSizeProgressive::ID: {
393 auto size = move_tl_object_as<telegram_api::photoSizeProgressive>(size_ptr);
394
395 if (size->sizes_.empty()) {
396 LOG(ERROR) << "Receive photo " << id << " from " << source << " with empty size " << to_string(size);
397 return std::move(res);
398 }
399 std::sort(size->sizes_.begin(), size->sizes_.end());
400
401 type = std::move(size->type_);
402 res.dimensions = get_dimensions(size->w_, size->h_, "photoSizeProgressive");
403 res.size = size->sizes_.back();
404 size->sizes_.pop_back();
405 res.progressive_sizes = std::move(size->sizes_);
406
407 break;
408 }
409 case telegram_api::photoPathSize::ID: {
410 auto size = move_tl_object_as<telegram_api::photoPathSize>(size_ptr);
411 if (format != PhotoFormat::Tgs && format != PhotoFormat::Webp) {
412 LOG(ERROR) << "Receive unexpected SVG minithumbnail in photo " << id << " from " << source << " of format "
413 << format;
414 return std::move(res);
415 }
416 return size->bytes_.as_slice().str();
417 }
418 default:
419 UNREACHABLE();
420 break;
421 }
422
423 if (type.size() != 1) {
424 LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
425 res.type = 0;
426 } else {
427 res.type = static_cast<uint8>(type[0]);
428 if (res.type >= 128) {
429 LOG(ERROR) << "Wrong photoSize \"" << type << "\" " << res;
430 res.type = 0;
431 }
432 }
433 if (source.get_type("get_photo_size") == PhotoSizeSource::Type::Thumbnail) {
434 source.thumbnail().thumbnail_type = res.type;
435 }
436
437 res.file_id = register_photo(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id,
438 res.size, dc_id, format);
439
440 if (!content.empty()) {
441 file_manager->set_content(res.file_id, std::move(content));
442 }
443
444 return std::move(res);
445 }
446
get_animation_size(FileManager * file_manager,PhotoSizeSource source,int64 id,int64 access_hash,std::string file_reference,DcId dc_id,DialogId owner_dialog_id,tl_object_ptr<telegram_api::videoSize> && size)447 AnimationSize get_animation_size(FileManager *file_manager, PhotoSizeSource source, int64 id, int64 access_hash,
448 std::string file_reference, DcId dc_id, DialogId owner_dialog_id,
449 tl_object_ptr<telegram_api::videoSize> &&size) {
450 CHECK(size != nullptr);
451 AnimationSize res;
452 if (size->type_ != "v" && size->type_ != "u") {
453 LOG(ERROR) << "Wrong videoSize \"" << size->type_ << "\" in " << to_string(size);
454 }
455 res.type = static_cast<uint8>(size->type_[0]);
456 if (res.type >= 128) {
457 LOG(ERROR) << "Wrong videoSize \"" << res.type << "\" " << res;
458 res.type = 0;
459 }
460 res.dimensions = get_dimensions(size->w_, size->h_, "get_animation_size");
461 res.size = size->size_;
462 if ((size->flags_ & telegram_api::videoSize::VIDEO_START_TS_MASK) != 0) {
463 res.main_frame_timestamp = size->video_start_ts_;
464 }
465
466 if (source.get_type("get_animation_size") == PhotoSizeSource::Type::Thumbnail) {
467 source.thumbnail().thumbnail_type = res.type;
468 }
469
470 res.file_id = register_photo(file_manager, source, id, access_hash, std::move(file_reference), owner_dialog_id,
471 res.size, dc_id, PhotoFormat::Mpeg4);
472 return res;
473 }
474
get_web_document_photo_size(FileManager * file_manager,FileType file_type,DialogId owner_dialog_id,tl_object_ptr<telegram_api::WebDocument> web_document_ptr)475 PhotoSize get_web_document_photo_size(FileManager *file_manager, FileType file_type, DialogId owner_dialog_id,
476 tl_object_ptr<telegram_api::WebDocument> web_document_ptr) {
477 if (web_document_ptr == nullptr) {
478 return {};
479 }
480
481 FileId file_id;
482 vector<tl_object_ptr<telegram_api::DocumentAttribute>> attributes;
483 int32 size = 0;
484 string mime_type;
485 switch (web_document_ptr->get_id()) {
486 case telegram_api::webDocument::ID: {
487 auto web_document = move_tl_object_as<telegram_api::webDocument>(web_document_ptr);
488 auto r_http_url = parse_url(web_document->url_);
489 if (r_http_url.is_error()) {
490 LOG(ERROR) << "Can't parse URL " << web_document->url_;
491 return {};
492 }
493 auto http_url = r_http_url.move_as_ok();
494 auto url = http_url.get_url();
495 file_id = file_manager->register_remote(FullRemoteFileLocation(file_type, url, web_document->access_hash_),
496 FileLocationSource::FromServer, owner_dialog_id, 0, web_document->size_,
497 get_url_query_file_name(http_url.query_));
498 size = web_document->size_;
499 mime_type = std::move(web_document->mime_type_);
500 attributes = std::move(web_document->attributes_);
501 break;
502 }
503 case telegram_api::webDocumentNoProxy::ID: {
504 auto web_document = move_tl_object_as<telegram_api::webDocumentNoProxy>(web_document_ptr);
505 if (web_document->url_.find('.') == string::npos) {
506 LOG(ERROR) << "Receive invalid URL " << web_document->url_;
507 return {};
508 }
509
510 auto r_file_id = file_manager->from_persistent_id(web_document->url_, file_type);
511 if (r_file_id.is_error()) {
512 LOG(ERROR) << "Can't register URL: " << r_file_id.error();
513 return {};
514 }
515 file_id = r_file_id.move_as_ok();
516
517 size = web_document->size_;
518 mime_type = std::move(web_document->mime_type_);
519 attributes = std::move(web_document->attributes_);
520 break;
521 }
522 default:
523 UNREACHABLE();
524 }
525 CHECK(file_id.is_valid());
526 bool is_animation = mime_type == "video/mp4";
527 bool is_gif = mime_type == "image/gif";
528
529 Dimensions dimensions;
530 for (auto &attribute : attributes) {
531 switch (attribute->get_id()) {
532 case telegram_api::documentAttributeImageSize::ID: {
533 auto image_size = move_tl_object_as<telegram_api::documentAttributeImageSize>(attribute);
534 dimensions = get_dimensions(image_size->w_, image_size->h_, "web documentAttributeImageSize");
535 break;
536 }
537 case telegram_api::documentAttributeAnimated::ID:
538 case telegram_api::documentAttributeHasStickers::ID:
539 case telegram_api::documentAttributeSticker::ID:
540 case telegram_api::documentAttributeVideo::ID:
541 case telegram_api::documentAttributeAudio::ID:
542 LOG(ERROR) << "Unexpected web document attribute " << to_string(attribute);
543 break;
544 case telegram_api::documentAttributeFilename::ID:
545 break;
546 default:
547 UNREACHABLE();
548 }
549 }
550
551 PhotoSize s;
552 s.type = is_animation ? 'v' : (is_gif ? 'g' : (file_type == FileType::Thumbnail ? 't' : 'n'));
553 s.dimensions = dimensions;
554 s.size = size;
555 s.file_id = file_id;
556 return s;
557 }
558
get_thumbnail_object(FileManager * file_manager,const PhotoSize & photo_size,PhotoFormat format)559 td_api::object_ptr<td_api::thumbnail> get_thumbnail_object(FileManager *file_manager, const PhotoSize &photo_size,
560 PhotoFormat format) {
561 if (!photo_size.file_id.is_valid()) {
562 return nullptr;
563 }
564
565 if (format == PhotoFormat::Jpeg && photo_size.type == 'g') {
566 format = PhotoFormat::Gif;
567 }
568
569 return td_api::make_object<td_api::thumbnail>(get_thumbnail_format_object(format), photo_size.dimensions.width,
570 photo_size.dimensions.height,
571 file_manager->get_file_object(photo_size.file_id));
572 }
573
get_photo_size_object(FileManager * file_manager,const PhotoSize * photo_size)574 static tl_object_ptr<td_api::photoSize> get_photo_size_object(FileManager *file_manager, const PhotoSize *photo_size) {
575 if (photo_size == nullptr || !photo_size->file_id.is_valid()) {
576 return nullptr;
577 }
578
579 return td_api::make_object<td_api::photoSize>(
580 photo_size->type ? std::string(1, static_cast<char>(photo_size->type))
581 : std::string(), // TODO replace string type with integer type
582 file_manager->get_file_object(photo_size->file_id), photo_size->dimensions.width, photo_size->dimensions.height,
583 vector<int32>(photo_size->progressive_sizes));
584 }
585
get_photo_sizes_object(FileManager * file_manager,const vector<PhotoSize> & photo_sizes)586 static vector<td_api::object_ptr<td_api::photoSize>> get_photo_sizes_object(FileManager *file_manager,
587 const vector<PhotoSize> &photo_sizes) {
588 auto sizes = transform(photo_sizes, [file_manager](const PhotoSize &photo_size) {
589 return get_photo_size_object(file_manager, &photo_size);
590 });
591 std::stable_sort(sizes.begin(), sizes.end(), [](const auto &lhs, const auto &rhs) {
592 if (lhs->photo_->expected_size_ != rhs->photo_->expected_size_) {
593 return lhs->photo_->expected_size_ < rhs->photo_->expected_size_;
594 }
595 return static_cast<uint32>(lhs->width_) * static_cast<uint32>(lhs->height_) <
596 static_cast<uint32>(rhs->width_) * static_cast<uint32>(rhs->height_);
597 });
598 td::remove_if(sizes, [](const auto &size) {
599 return !size->photo_->local_->can_be_downloaded_ && !size->photo_->local_->is_downloading_completed_;
600 });
601 return sizes;
602 }
603
operator ==(const PhotoSize & lhs,const PhotoSize & rhs)604 bool operator==(const PhotoSize &lhs, const PhotoSize &rhs) {
605 return lhs.type == rhs.type && lhs.dimensions == rhs.dimensions && lhs.size == rhs.size &&
606 lhs.file_id == rhs.file_id && lhs.progressive_sizes == rhs.progressive_sizes;
607 }
608
operator !=(const PhotoSize & lhs,const PhotoSize & rhs)609 bool operator!=(const PhotoSize &lhs, const PhotoSize &rhs) {
610 return !(lhs == rhs);
611 }
612
operator <(const PhotoSize & lhs,const PhotoSize & rhs)613 bool operator<(const PhotoSize &lhs, const PhotoSize &rhs) {
614 if (lhs.size != rhs.size) {
615 return lhs.size < rhs.size;
616 }
617 auto lhs_pixels = get_pixel_count(lhs.dimensions);
618 auto rhs_pixels = get_pixel_count(rhs.dimensions);
619 if (lhs_pixels != rhs_pixels) {
620 return lhs_pixels < rhs_pixels;
621 }
622 int32 lhs_type = lhs.type == 't' ? -1 : lhs.type;
623 int32 rhs_type = rhs.type == 't' ? -1 : rhs.type;
624 if (lhs_type != rhs_type) {
625 return lhs_type < rhs_type;
626 }
627 if (lhs.file_id != rhs.file_id) {
628 return lhs.file_id.get() < rhs.file_id.get();
629 }
630 return lhs.dimensions.width < rhs.dimensions.width;
631 }
632
operator <<(StringBuilder & string_builder,const PhotoSize & photo_size)633 StringBuilder &operator<<(StringBuilder &string_builder, const PhotoSize &photo_size) {
634 return string_builder << "{type = " << photo_size.type << ", dimensions = " << photo_size.dimensions
635 << ", size = " << photo_size.size << ", file_id = " << photo_size.file_id
636 << ", progressive_sizes = " << photo_size.progressive_sizes << "}";
637 }
638
get_animated_chat_photo_object(FileManager * file_manager,const AnimationSize * animation_size)639 static tl_object_ptr<td_api::animatedChatPhoto> get_animated_chat_photo_object(FileManager *file_manager,
640 const AnimationSize *animation_size) {
641 if (animation_size == nullptr || !animation_size->file_id.is_valid()) {
642 return nullptr;
643 }
644
645 return td_api::make_object<td_api::animatedChatPhoto>(animation_size->dimensions.width,
646 file_manager->get_file_object(animation_size->file_id),
647 animation_size->main_frame_timestamp);
648 }
649
operator ==(const AnimationSize & lhs,const AnimationSize & rhs)650 bool operator==(const AnimationSize &lhs, const AnimationSize &rhs) {
651 return static_cast<const PhotoSize &>(lhs) == static_cast<const PhotoSize &>(rhs) &&
652 fabs(lhs.main_frame_timestamp - rhs.main_frame_timestamp) < 1e-3;
653 }
654
operator !=(const AnimationSize & lhs,const AnimationSize & rhs)655 bool operator!=(const AnimationSize &lhs, const AnimationSize &rhs) {
656 return !(lhs == rhs);
657 }
658
operator <<(StringBuilder & string_builder,const AnimationSize & animation_size)659 StringBuilder &operator<<(StringBuilder &string_builder, const AnimationSize &animation_size) {
660 return string_builder << static_cast<const PhotoSize &>(animation_size) << " from "
661 << animation_size.main_frame_timestamp;
662 }
663
get_encrypted_file_photo(FileManager * file_manager,unique_ptr<EncryptedFile> && file,tl_object_ptr<secret_api::decryptedMessageMediaPhoto> && photo,DialogId owner_dialog_id)664 Photo get_encrypted_file_photo(FileManager *file_manager, unique_ptr<EncryptedFile> &&file,
665 tl_object_ptr<secret_api::decryptedMessageMediaPhoto> &&photo,
666 DialogId owner_dialog_id) {
667 FileId file_id = file_manager->register_remote(
668 FullRemoteFileLocation(FileType::Encrypted, file->id_, file->access_hash_, DcId::create(file->dc_id_), string()),
669 FileLocationSource::FromServer, owner_dialog_id, photo->size_, 0,
670 PSTRING() << static_cast<uint64>(file->id_) << ".jpg");
671 file_manager->set_encryption_key(file_id, FileEncryptionKey{photo->key_.as_slice(), photo->iv_.as_slice()});
672
673 Photo res;
674 res.id = 0;
675 res.date = 0;
676
677 if (!photo->thumb_.empty()) {
678 res.photos.push_back(get_secret_thumbnail_photo_size(file_manager, std::move(photo->thumb_), owner_dialog_id,
679 photo->thumb_w_, photo->thumb_h_));
680 }
681
682 PhotoSize s;
683 s.type = 'i';
684 s.dimensions = get_dimensions(photo->w_, photo->h_, "get_encrypted_file_photo");
685 s.size = photo->size_;
686 s.file_id = file_id;
687 res.photos.push_back(s);
688
689 return res;
690 }
691
get_photo(FileManager * file_manager,tl_object_ptr<telegram_api::Photo> && photo,DialogId owner_dialog_id)692 Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::Photo> &&photo, DialogId owner_dialog_id) {
693 if (photo == nullptr || photo->get_id() == telegram_api::photoEmpty::ID) {
694 return Photo();
695 }
696 CHECK(photo->get_id() == telegram_api::photo::ID);
697 return get_photo(file_manager, move_tl_object_as<telegram_api::photo>(photo), owner_dialog_id);
698 }
699
get_photo(FileManager * file_manager,tl_object_ptr<telegram_api::photo> && photo,DialogId owner_dialog_id)700 Photo get_photo(FileManager *file_manager, tl_object_ptr<telegram_api::photo> &&photo, DialogId owner_dialog_id) {
701 CHECK(photo != nullptr);
702 Photo res;
703
704 res.id = photo->id_;
705 res.date = photo->date_;
706 res.has_stickers = photo->has_stickers_;
707
708 if (res.is_empty()) {
709 LOG(ERROR) << "Receive photo with identifier " << res.id.get();
710 res.id = -3;
711 }
712
713 DcId dc_id = DcId::create(photo->dc_id_);
714 for (auto &size_ptr : photo->sizes_) {
715 auto photo_size = get_photo_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_,
716 photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id,
717 owner_dialog_id, std::move(size_ptr), PhotoFormat::Jpeg);
718 if (photo_size.get_offset() == 0) {
719 PhotoSize &size = photo_size.get<0>();
720 if (size.type == 0 || size.type == 't' || size.type == 'i' || size.type == 'u' || size.type == 'v') {
721 LOG(ERROR) << "Skip unallowed photo size " << size;
722 continue;
723 }
724 res.photos.push_back(std::move(size));
725 } else {
726 res.minithumbnail = std::move(photo_size.get<1>());
727 }
728 }
729
730 for (auto &size_ptr : photo->video_sizes_) {
731 auto animation = get_animation_size(file_manager, PhotoSizeSource::thumbnail(FileType::Photo, 0), photo->id_,
732 photo->access_hash_, photo->file_reference_.as_slice().str(), dc_id,
733 owner_dialog_id, std::move(size_ptr));
734 if (animation.type != 0 && animation.dimensions.width == animation.dimensions.height) {
735 res.animations.push_back(std::move(animation));
736 }
737 }
738
739 return res;
740 }
741
get_web_document_photo(FileManager * file_manager,tl_object_ptr<telegram_api::WebDocument> web_document,DialogId owner_dialog_id)742 Photo get_web_document_photo(FileManager *file_manager, tl_object_ptr<telegram_api::WebDocument> web_document,
743 DialogId owner_dialog_id) {
744 PhotoSize s = get_web_document_photo_size(file_manager, FileType::Photo, owner_dialog_id, std::move(web_document));
745 Photo photo;
746 if (s.file_id.is_valid() && s.type != 'v' && s.type != 'g') {
747 photo.id = 0;
748 photo.photos.push_back(s);
749 }
750 return photo;
751 }
752
get_photo_object(FileManager * file_manager,const Photo & photo)753 tl_object_ptr<td_api::photo> get_photo_object(FileManager *file_manager, const Photo &photo) {
754 if (photo.is_empty()) {
755 return nullptr;
756 }
757
758 return td_api::make_object<td_api::photo>(photo.has_stickers, get_minithumbnail_object(photo.minithumbnail),
759 get_photo_sizes_object(file_manager, photo.photos));
760 }
761
get_chat_photo_object(FileManager * file_manager,const Photo & photo)762 tl_object_ptr<td_api::chatPhoto> get_chat_photo_object(FileManager *file_manager, const Photo &photo) {
763 if (photo.is_empty()) {
764 return nullptr;
765 }
766
767 const AnimationSize *animation = photo.animations.empty() ? nullptr : &photo.animations.back();
768 return td_api::make_object<td_api::chatPhoto>(
769 photo.id.get(), photo.date, get_minithumbnail_object(photo.minithumbnail),
770 get_photo_sizes_object(file_manager, photo.photos), get_animated_chat_photo_object(file_manager, animation));
771 }
772
photo_delete_thumbnail(Photo & photo)773 void photo_delete_thumbnail(Photo &photo) {
774 for (size_t i = 0; i < photo.photos.size(); i++) {
775 if (photo.photos[i].type == 't') {
776 photo.photos.erase(photo.photos.begin() + i);
777 return;
778 }
779 }
780 }
781
photo_has_input_media(FileManager * file_manager,const Photo & photo,bool is_secret,bool is_bot)782 bool photo_has_input_media(FileManager *file_manager, const Photo &photo, bool is_secret, bool is_bot) {
783 if (photo.photos.empty() || photo.photos.back().type != 'i') {
784 LOG(ERROR) << "Wrong photo: " << photo;
785 return false;
786 }
787 auto file_id = photo.photos.back().file_id;
788 auto file_view = file_manager->get_file_view(file_id);
789 if (is_secret) {
790 if (!file_view.is_encrypted_secret() || !file_view.has_remote_location()) {
791 return false;
792 }
793
794 for (const auto &size : photo.photos) {
795 if (size.type == 't' && size.file_id.is_valid()) {
796 return false;
797 }
798 }
799
800 return true;
801 } else {
802 if (file_view.is_encrypted()) {
803 return false;
804 }
805 if (is_bot && file_view.has_remote_location()) {
806 return true;
807 }
808 return /* file_view.has_remote_location() || */ file_view.has_url();
809 }
810 }
811
photo_get_input_media(FileManager * file_manager,const Photo & photo,tl_object_ptr<telegram_api::InputFile> input_file,int32 ttl)812 tl_object_ptr<telegram_api::InputMedia> photo_get_input_media(FileManager *file_manager, const Photo &photo,
813 tl_object_ptr<telegram_api::InputFile> input_file,
814 int32 ttl) {
815 if (!photo.photos.empty()) {
816 auto file_id = photo.photos.back().file_id;
817 auto file_view = file_manager->get_file_view(file_id);
818 if (file_view.is_encrypted()) {
819 return nullptr;
820 }
821 if (file_view.has_remote_location() && !file_view.main_remote_location().is_web() && input_file == nullptr) {
822 int32 flags = 0;
823 if (ttl != 0) {
824 flags |= telegram_api::inputMediaPhoto::TTL_SECONDS_MASK;
825 }
826 return make_tl_object<telegram_api::inputMediaPhoto>(flags, file_view.main_remote_location().as_input_photo(),
827 ttl);
828 }
829 if (file_view.has_url()) {
830 int32 flags = 0;
831 if (ttl != 0) {
832 flags |= telegram_api::inputMediaPhotoExternal::TTL_SECONDS_MASK;
833 }
834 LOG(INFO) << "Create inputMediaPhotoExternal with a URL " << file_view.url() << " and TTL " << ttl;
835 return make_tl_object<telegram_api::inputMediaPhotoExternal>(flags, file_view.url(), ttl);
836 }
837 if (input_file == nullptr) {
838 CHECK(!file_view.has_remote_location());
839 }
840 }
841 if (input_file != nullptr) {
842 int32 flags = 0;
843 vector<tl_object_ptr<telegram_api::InputDocument>> added_stickers;
844 if (photo.has_stickers) {
845 flags |= telegram_api::inputMediaUploadedPhoto::STICKERS_MASK;
846 added_stickers = file_manager->get_input_documents(photo.sticker_file_ids);
847 }
848 if (ttl != 0) {
849 flags |= telegram_api::inputMediaUploadedPhoto::TTL_SECONDS_MASK;
850 }
851
852 return make_tl_object<telegram_api::inputMediaUploadedPhoto>(flags, std::move(input_file),
853 std::move(added_stickers), ttl);
854 }
855 return nullptr;
856 }
857
photo_get_secret_input_media(FileManager * file_manager,const Photo & photo,tl_object_ptr<telegram_api::InputEncryptedFile> input_file,const string & caption,BufferSlice thumbnail)858 SecretInputMedia photo_get_secret_input_media(FileManager *file_manager, const Photo &photo,
859 tl_object_ptr<telegram_api::InputEncryptedFile> input_file,
860 const string &caption, BufferSlice thumbnail) {
861 FileId file_id;
862 int32 width = 0;
863 int32 height = 0;
864
865 FileId thumbnail_file_id;
866 int32 thumbnail_width = 0;
867 int32 thumbnail_height = 0;
868 for (const auto &size : photo.photos) {
869 if (size.type == 'i') {
870 file_id = size.file_id;
871 width = size.dimensions.width;
872 height = size.dimensions.height;
873 }
874 if (size.type == 't') {
875 thumbnail_file_id = size.file_id;
876 thumbnail_width = size.dimensions.width;
877 thumbnail_height = size.dimensions.height;
878 }
879 }
880 if (file_id.empty()) {
881 LOG(ERROR) << "NO SIZE";
882 return {};
883 }
884 auto file_view = file_manager->get_file_view(file_id);
885 auto &encryption_key = file_view.encryption_key();
886 if (!file_view.is_encrypted_secret() || encryption_key.empty()) {
887 return {};
888 }
889 if (file_view.has_remote_location()) {
890 LOG(INFO) << "Photo has remote location";
891 input_file = file_view.main_remote_location().as_input_encrypted_file();
892 }
893 if (input_file == nullptr) {
894 return {};
895 }
896 if (thumbnail_file_id.is_valid() && thumbnail.empty()) {
897 return {};
898 }
899
900 return SecretInputMedia{
901 std::move(input_file),
902 make_tl_object<secret_api::decryptedMessageMediaPhoto>(
903 std::move(thumbnail), thumbnail_width, thumbnail_height, width, height, narrow_cast<int32>(file_view.size()),
904 BufferSlice(encryption_key.key_slice()), BufferSlice(encryption_key.iv_slice()), caption)};
905 }
906
photo_get_file_ids(const Photo & photo)907 vector<FileId> photo_get_file_ids(const Photo &photo) {
908 auto result = transform(photo.photos, [](auto &size) { return size.file_id; });
909 if (!photo.animations.empty()) {
910 // photo file IDs must be first
911 append(result, transform(photo.animations, [](auto &size) { return size.file_id; }));
912 }
913 return result;
914 }
915
operator ==(const Photo & lhs,const Photo & rhs)916 bool operator==(const Photo &lhs, const Photo &rhs) {
917 return lhs.id.get() == rhs.id.get() && lhs.photos == rhs.photos && lhs.animations == rhs.animations;
918 }
919
operator !=(const Photo & lhs,const Photo & rhs)920 bool operator!=(const Photo &lhs, const Photo &rhs) {
921 return !(lhs == rhs);
922 }
923
operator <<(StringBuilder & string_builder,const Photo & photo)924 StringBuilder &operator<<(StringBuilder &string_builder, const Photo &photo) {
925 string_builder << "[ID = " << photo.id.get() << ", photos = " << format::as_array(photo.photos);
926 if (!photo.animations.empty()) {
927 string_builder << ", animations = " << format::as_array(photo.animations);
928 }
929 return string_builder << ']';
930 }
931
convert_photo_to_profile_photo(const tl_object_ptr<telegram_api::photo> & photo)932 tl_object_ptr<telegram_api::userProfilePhoto> convert_photo_to_profile_photo(
933 const tl_object_ptr<telegram_api::photo> &photo) {
934 if (photo == nullptr) {
935 return nullptr;
936 }
937
938 bool have_photo_small = false;
939 bool have_photo_big = false;
940 for (auto &size_ptr : photo->sizes_) {
941 switch (size_ptr->get_id()) {
942 case telegram_api::photoSizeEmpty::ID:
943 break;
944 case telegram_api::photoSize::ID: {
945 auto size = static_cast<const telegram_api::photoSize *>(size_ptr.get());
946 if (size->type_ == "a") {
947 have_photo_small = true;
948 } else if (size->type_ == "c") {
949 have_photo_big = true;
950 }
951 break;
952 }
953 case telegram_api::photoCachedSize::ID: {
954 auto size = static_cast<const telegram_api::photoCachedSize *>(size_ptr.get());
955 if (size->type_ == "a") {
956 have_photo_small = true;
957 } else if (size->type_ == "c") {
958 have_photo_big = true;
959 }
960 break;
961 }
962 case telegram_api::photoStrippedSize::ID:
963 break;
964 case telegram_api::photoSizeProgressive::ID: {
965 auto size = static_cast<const telegram_api::photoSizeProgressive *>(size_ptr.get());
966 if (size->type_ == "a") {
967 have_photo_small = true;
968 } else if (size->type_ == "c") {
969 have_photo_big = true;
970 }
971 break;
972 }
973 default:
974 UNREACHABLE();
975 break;
976 }
977 }
978 if (!have_photo_small || !have_photo_big) {
979 return nullptr;
980 }
981 bool has_video = !photo->video_sizes_.empty();
982 return make_tl_object<telegram_api::userProfilePhoto>(0, has_video, photo->id_, BufferSlice(), photo->dc_id_);
983 }
984
985 } // namespace td
986