Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file tl_skip.c
6 : * @brief TL object skippers for vector iteration.
7 : */
8 :
9 : #include "tl_skip.h"
10 : #include "tl_registry.h"
11 : #include "logger.h"
12 :
13 : #include <stdlib.h>
14 : #include <string.h>
15 :
16 : /* ---- CRCs not yet in tl_registry.h ---- */
17 :
18 : #define CRC_peerNotifySettings 0xa83b0426u
19 : #define CRC_notificationSoundDefault 0x97e8bebeu
20 : #define CRC_notificationSoundNone 0x6f0c34dfu
21 : #define CRC_notificationSoundLocal 0x830b9ae4u
22 : #define CRC_notificationSoundRingtone 0xff6c8049u
23 : #define CRC_draftMessage 0x3fccf7efu
24 : #define CRC_draftMessageEmpty 0x1b0c841au
25 :
26 : /* MessageEntity variants (layer 170+). */
27 : #define CRC_messageEntityUnknown 0xbb92ba95u
28 : #define CRC_messageEntityMention 0xfa04579du
29 : #define CRC_messageEntityHashtag 0x6f635b0du
30 : #define CRC_messageEntityBotCommand 0x6cef8ac7u
31 : #define CRC_messageEntityUrl 0x6ed02538u
32 : #define CRC_messageEntityEmail 0x64e475c2u
33 : #define CRC_messageEntityBold 0xbd610bc9u
34 : #define CRC_messageEntityItalic 0x826f8b60u
35 : #define CRC_messageEntityCode 0x28a20571u
36 : #define CRC_messageEntityPre 0x73924be0u
37 : #define CRC_messageEntityTextUrl 0x76a6d327u
38 : #define CRC_messageEntityMentionName 0xdc7b1140u
39 : #define CRC_messageEntityPhone 0x9b69e34bu
40 : #define CRC_messageEntityCashtag 0x4c4e743fu
41 : #define CRC_messageEntityUnderline 0x9c4e7e8bu
42 : #define CRC_messageEntityStrike 0xbf0693d4u
43 : #define CRC_messageEntityBlockquote 0xf1ccaaacu
44 : #define CRC_messageEntityBankCard 0x761e6af4u
45 : #define CRC_messageEntitySpoiler 0x32ca960fu
46 : #define CRC_messageEntityCustomEmoji 0xc8cf05f8u
47 :
48 : /* MessageFwdHeader (layer 170+). */
49 : #define CRC_messageFwdHeader 0x4e4df4bbu
50 :
51 : /* MessageReplyHeader variants. */
52 : #define CRC_messageReplyHeader 0xafbc09dbu
53 : #define CRC_messageReplyStoryHeader 0xe5af939u
54 :
55 : /* PhotoSize variants (layer 170+). */
56 : #define CRC_photoSizeEmpty 0x0e17e23cu
57 : #define CRC_photoSize 0x75c78e60u
58 : #define CRC_photoCachedSize 0x021e1ad6u
59 : #define CRC_photoStrippedSize 0xe0b0bc2eu
60 : #define CRC_photoSizeProgressive 0xfa3efb95u
61 : #define CRC_photoPathSize 0xd8214d41u
62 :
63 : /* Photo variants. */
64 : #define CRC_photo 0xfb197a65u
65 : #define CRC_photoEmpty 0x2331b22du
66 :
67 : /* Document variants. */
68 : #define CRC_document 0x8fd4c4d8u
69 : #define CRC_documentEmpty 0x36f8c871u
70 :
71 : /* DocumentAttribute — present inside document.attributes. We don't walk
72 : * them; we just skip via Vector count by bailing when document is set. */
73 :
74 : /* GeoPoint. */
75 : #define CRC_geoPointEmpty 0x1117dd5fu
76 : #define CRC_geoPoint 0xb2a2f663u
77 :
78 : /* MessageMedia variants. */
79 : #define CRC_messageMediaEmpty 0x3ded6320u
80 : #define CRC_messageMediaPhoto 0x695150d7u
81 : #define CRC_messageMediaDocument 0x4cf4d72du
82 : #define CRC_messageMediaGeo 0x56e0d474u
83 : #define CRC_messageMediaContact 0x70322949u
84 : #define CRC_messageMediaUnsupported 0x9f84f49eu
85 : #define CRC_messageMediaVenue 0x2ec0533fu
86 : #define CRC_messageMediaGeoLive 0xb940c666u
87 : #define CRC_messageMediaDice 0x3f7ee58bu
88 : #define CRC_messageMediaWebPage 0xddf8c26eu
89 : #define CRC_messageMediaPoll 0x4bd6e798u
90 : #define CRC_messageMediaInvoice 0xf6a548d3u
91 : #define CRC_messageMediaStory 0x68cb6283u
92 : #define CRC_messageMediaGiveaway 0xaa073beeu
93 : #define CRC_messageMediaGame 0xfdb19008u
94 : #define CRC_messageMediaPaidMedia 0xa8852491u
95 :
96 : /* Game (inside messageMediaGame). */
97 : #define CRC_game 0xbdf9653bu
98 :
99 : /* MessageExtendedMedia (inside messageMediaPaidMedia). */
100 : #define CRC_messageExtendedMediaPreview 0xad628cc8u
101 : #define CRC_messageExtendedMedia 0xee479c64u
102 :
103 : /* WebDocument (inside messageMediaInvoice.photo). */
104 : #define CRC_webDocument 0x1c570ed1u
105 : #define CRC_webDocumentNoProxy 0xf9c8bcc6u
106 :
107 : /* StoryItem variants (inside messageMediaStory when flags.0). */
108 : #define CRC_storyItemDeleted 0x51e6ee4fu
109 : #define CRC_storyItemSkipped 0xffadc913u
110 : #define CRC_storyItem 0x79b26a24u
111 :
112 : /* StoryFwdHeader (inside full storyItem.fwd_from). */
113 : #define CRC_storyFwdHeader 0xb826e150u
114 :
115 : /* MediaAreaCoordinates (inner of every MediaArea). */
116 : #define CRC_mediaAreaCoordinates 0x03d1ea4eu
117 :
118 : /* GeoPointAddress (inside mediaAreaGeoPoint when flags.0). */
119 : #define CRC_geoPointAddress 0xde4c5d93u
120 :
121 : /* MediaArea variants. */
122 : #define CRC_mediaAreaVenue 0xbe82db9cu
123 : #define CRC_mediaAreaGeoPoint 0xdf8b3b22u
124 : #define CRC_mediaAreaSuggestedReaction 0x14455871u
125 : #define CRC_mediaAreaChannelPost 0x770416afu
126 : #define CRC_mediaAreaUrl 0x37381085u
127 : #define CRC_mediaAreaWeather 0x49a6549cu
128 : #define CRC_mediaAreaStarGift 0x5787686du
129 :
130 : /* PrivacyRule variants. */
131 : #define CRC_privacyValueAllowContacts 0xfffe1bacu
132 : #define CRC_privacyValueAllowAll 0x65427b82u
133 : #define CRC_privacyValueAllowUsers 0xb8905fb2u
134 : #define CRC_privacyValueDisallowContacts 0xf888fa1au
135 : #define CRC_privacyValueDisallowAll 0x8b73e763u
136 : #define CRC_privacyValueDisallowUsers 0xe4621141u
137 : #define CRC_privacyValueAllowChatParticipants 0x6b134e8eu
138 : #define CRC_privacyValueDisallowChatParticipants 0x41c87565u
139 : #define CRC_privacyValueAllowCloseFriends 0xf7e8d89bu
140 : #define CRC_privacyValueAllowPremium 0xece9814bu
141 : #define CRC_privacyValueAllowBots 0x21461b5du
142 : #define CRC_privacyValueDisallowBots 0xf6a5f82fu
143 :
144 : /* StoryViews. */
145 : #define CRC_storyViews 0x8d595cd6u
146 :
147 : /* WebPage variants (inside messageMediaWebPage). */
148 : #define CRC_webPage 0xe89c45b2u
149 : #define CRC_webPageEmpty 0xeb1477e8u
150 : #define CRC_webPagePending 0xb0d13e47u
151 : #define CRC_webPageNotModified 0x7311ca11u
152 :
153 : /* WebPageAttribute variants (webPage.attributes, flags.12). */
154 : #define CRC_webPageAttributeTheme 0x54b56617u
155 : #define CRC_webPageAttributeStory 0x2e94c3e7u
156 : #define CRC_webPageAttributeStickerSet 0x50cc03d3u
157 :
158 : /* Page (webPage.cached_page, flags.10). */
159 : #define CRC_page 0x98657f0du
160 :
161 : /* PageBlock variants — full set for cached_page iteration. */
162 : #define CRC_pageBlockUnsupported 0x13567e8au
163 : #define CRC_pageBlockTitle 0x70abc3fdu
164 : #define CRC_pageBlockSubtitle 0x8ffa9a1fu
165 : #define CRC_pageBlockHeader 0xbfd064ecu
166 : #define CRC_pageBlockSubheader 0xf12bb6e1u
167 : #define CRC_pageBlockKicker 0x1e148390u
168 : #define CRC_pageBlockParagraph 0x467a0766u
169 : #define CRC_pageBlockPreformatted 0xc070d93eu
170 : #define CRC_pageBlockFooter 0x48870999u
171 : #define CRC_pageBlockDivider 0xdb20b188u
172 : #define CRC_pageBlockAnchor 0xce0d37b0u
173 : #define CRC_pageBlockAuthorDate 0xbaafe5e0u
174 : #define CRC_pageBlockBlockquote 0x263d7c26u
175 : #define CRC_pageBlockPullquote 0x4f4456d5u
176 : #define CRC_pageBlockPhoto 0x1759c560u
177 : #define CRC_pageBlockVideo 0x7c8fe7b6u
178 : #define CRC_pageBlockAudio 0x804361eau
179 : #define CRC_pageBlockCover 0x39f23300u
180 : #define CRC_pageBlockChannel 0xef1751b5u
181 : #define CRC_pageBlockMap 0xa44f3ef6u
182 : #define CRC_pageBlockList 0xe4e88011u
183 : #define CRC_pageBlockOrderedList 0x9a8ae1e1u
184 : #define CRC_pageBlockCollage 0x65a0fa4du
185 : #define CRC_pageBlockSlideshow 0x031f9590u
186 : #define CRC_pageBlockDetails 0x76768bedu
187 : #define CRC_pageBlockRelatedArticles 0x16115a96u
188 : #define CRC_pageBlockTable 0xbf4dea82u
189 : #define CRC_pageBlockEmbed 0xa8718dc5u
190 : #define CRC_pageBlockEmbedPost 0xf259a80bu
191 :
192 : /* PageCaption, PageListItem, PageListOrderedItem, PageTableRow/Cell,
193 : * PageRelatedArticle — all reached from PageBlock variants. */
194 : #define CRC_pageCaption 0x6f747657u
195 : #define CRC_pageListItemText 0xb92fb6cdu
196 : #define CRC_pageListItemBlocks 0x25e073fcu
197 : #define CRC_pageListOrderedItemText 0x5e068047u
198 : #define CRC_pageListOrderedItemBlocks 0x98dd8936u
199 : #define CRC_pageTableRow 0xe0c0c5e5u
200 : #define CRC_pageTableCell 0x34566b6au
201 : #define CRC_pageRelatedArticle 0xb390dc08u
202 :
203 : /* RichText variants — full tree so every supported PageBlock can walk
204 : * its RichText payload. */
205 : #define CRC_textEmpty 0xdc3d824fu
206 : #define CRC_textPlain 0x744694e0u
207 : #define CRC_textBold 0x6724abc4u
208 : #define CRC_textItalic 0xd912a59cu
209 : #define CRC_textUnderline 0xc12622c4u
210 : #define CRC_textStrike 0x9bf8bb95u
211 : #define CRC_textFixed 0x6c3f19b9u
212 : #define CRC_textUrl 0x3c2884c1u
213 : #define CRC_textEmail 0xde5a0dd6u
214 : #define CRC_textConcat 0x7e6260d7u
215 : #define CRC_textSubscript 0xed6a8504u
216 : #define CRC_textSuperscript 0xc7fb5e01u
217 : #define CRC_textMarked 0x034b27f6u
218 : #define CRC_textPhone 0x1ccb966au
219 : #define CRC_textImage 0x081ccf4fu
220 : #define CRC_textAnchor 0x35553762u
221 :
222 : /* Poll + PollAnswer + PollResults + PollAnswerVoters. */
223 : #define CRC_poll 0x58747131u
224 : #define CRC_pollAnswer 0x6ca9c2e9u
225 : #define CRC_pollResults 0x7adc669du
226 : #define CRC_pollAnswerVoters 0x3b6ddad2u
227 : #define CRC_textWithEntities_poll 0x751f3146u
228 :
229 : /* ChatPhoto. */
230 : #define CRC_chatPhotoEmpty 0x37c1011cu
231 : #define CRC_chatPhoto 0x1c6e1c11u
232 :
233 : /* UserProfilePhoto. */
234 : #define CRC_userProfilePhotoEmpty 0x4f11bae1u
235 : #define CRC_userProfilePhoto 0x82d1f706u
236 :
237 : /* UserStatus. */
238 : #define CRC_userStatusEmpty 0x09d05049u
239 : #define CRC_userStatusOnline 0xedb93949u
240 : #define CRC_userStatusOffline 0x008c703fu
241 : #define CRC_userStatusRecently 0x7b197dc8u
242 : #define CRC_userStatusLastWeek 0x541a1d1au
243 : #define CRC_userStatusLastMonth 0x65899e67u
244 :
245 : /* RestrictionReason. */
246 : #define CRC_restrictionReason 0xd072acb4u
247 :
248 : /* Username. */
249 : #define CRC_username 0xb4073647u
250 :
251 : /* PeerColor. */
252 : #define CRC_peerColor 0xb54b5acfu
253 :
254 : /* EmojiStatus. */
255 : #define CRC_emojiStatusEmpty 0x2de11aaeu
256 : #define CRC_emojiStatus 0x929b619du
257 : #define CRC_emojiStatusUntil 0xfa30a8c7u
258 :
259 : /* ChatAdminRights / ChatBannedRights. */
260 : #define CRC_chatAdminRights 0x5fb224d5u
261 : #define CRC_chatBannedRights 0x9f120418u
262 :
263 : /* ReplyMarkup variants. */
264 : #define CRC_replyKeyboardHide 0xa03e5b85u
265 : #define CRC_replyKeyboardForceReply 0x86b40b08u
266 : #define CRC_replyKeyboardMarkup 0x85dd99d1u
267 : #define CRC_replyInlineMarkup 0x48a30254u
268 :
269 : /* KeyboardButtonRow. */
270 : #define CRC_keyboardButtonRow 0x77608b83u
271 :
272 : /* KeyboardButton variants we handle inline. */
273 : #define CRC_keyboardButton 0xa2fa4880u
274 : #define CRC_keyboardButtonUrl 0x258aff05u
275 : #define CRC_keyboardButtonCallback 0x35bbdb6bu
276 : #define CRC_keyboardButtonRequestPhone 0xb16a6c29u
277 : #define CRC_keyboardButtonRequestGeoLoc 0xfc796b3fu
278 : #define CRC_keyboardButtonSwitchInline 0x93b9fbb5u
279 : #define CRC_keyboardButtonGame 0x50f41ccfu
280 : #define CRC_keyboardButtonBuy 0xafd93fbbu
281 : #define CRC_keyboardButtonUrlAuth 0x10b78d29u
282 : #define CRC_keyboardButtonRequestPoll 0xbbc7515du
283 : #define CRC_keyboardButtonUserProfile 0x308660c1u
284 : #define CRC_keyboardButtonWebView 0x13767230u
285 : #define CRC_keyboardButtonSimpleWebView 0xa0c0505cu
286 :
287 : /* MessageReplies. */
288 : #define CRC_messageReplies 0x83d60fc2u
289 :
290 : /* FactCheck + TextWithEntities. */
291 : #define CRC_factCheck 0xb89bfccfu
292 : #define CRC_textWithEntities 0x751f3146u
293 :
294 : /* MessageReactions + Reaction variants. */
295 : #define CRC_messageReactions 0x4f2b9479u
296 : #define CRC_reactionCount 0xa3d1cb80u
297 : #define CRC_reactionEmpty 0x79f5d419u
298 : #define CRC_reactionEmoji 0x1b2286b8u
299 : #define CRC_reactionCustomEmoji 0x8935fc73u
300 : #define CRC_reactionPaid 0x523da4ebu
301 :
302 17 : int tl_skip_bool(TlReader *r) {
303 17 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
304 14 : tl_read_uint32(r);
305 14 : return 0;
306 : }
307 :
308 4184 : int tl_skip_string(TlReader *r) {
309 : /* tl_read_string already advances the cursor and handles padding. */
310 4184 : if (!tl_reader_ok(r)) return -1;
311 4182 : char *s = tl_read_string(r);
312 4182 : if (!s) return -1;
313 4182 : free(s);
314 4182 : return 0;
315 : }
316 :
317 2321 : int tl_skip_peer(TlReader *r) {
318 2321 : if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
319 2318 : uint32_t crc = tl_read_uint32(r);
320 2318 : (void)tl_read_int64(r);
321 2318 : switch (crc) {
322 2315 : case TL_peerUser:
323 : case TL_peerChat:
324 : case TL_peerChannel:
325 2315 : return 0;
326 3 : default:
327 3 : logger_log(LOG_WARN, "tl_skip_peer: unknown Peer 0x%08x", crc);
328 3 : return -1;
329 : }
330 : }
331 :
332 21 : int tl_skip_notification_sound(TlReader *r) {
333 21 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
334 21 : uint32_t crc = tl_read_uint32(r);
335 21 : switch (crc) {
336 12 : case CRC_notificationSoundDefault:
337 : case CRC_notificationSoundNone:
338 12 : return 0; /* no payload */
339 3 : case CRC_notificationSoundRingtone:
340 3 : if (r->len - r->pos < 8) return -1;
341 3 : tl_read_int64(r); /* id */
342 3 : return 0;
343 3 : case CRC_notificationSoundLocal:
344 3 : if (tl_skip_string(r) != 0) return -1; /* title */
345 3 : if (tl_skip_string(r) != 0) return -1; /* data */
346 3 : return 0;
347 3 : default:
348 3 : logger_log(LOG_WARN, "tl_skip_notification_sound: unknown 0x%08x", crc);
349 3 : return -1;
350 : }
351 : }
352 :
353 : /* peerNotifySettings#a83b0426 flags:#
354 : * show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int
355 : * ios_sound:flags.3?NotificationSound
356 : * android_sound:flags.4?NotificationSound
357 : * other_sound:flags.5?NotificationSound
358 : * stories_muted:flags.6?Bool stories_hide_sender:flags.7?Bool
359 : * stories_ios_sound:flags.8?NotificationSound
360 : * stories_android_sound:flags.9?NotificationSound
361 : * stories_other_sound:flags.10?NotificationSound
362 : */
363 853 : int tl_skip_peer_notify_settings(TlReader *r) {
364 853 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
365 853 : uint32_t crc = tl_read_uint32(r);
366 853 : if (crc != CRC_peerNotifySettings) {
367 3 : logger_log(LOG_WARN,
368 : "tl_skip_peer_notify_settings: unexpected 0x%08x", crc);
369 3 : return -1;
370 : }
371 850 : uint32_t flags = tl_read_uint32(r);
372 :
373 850 : if (flags & (1u << 0)) if (tl_skip_bool(r) != 0) return -1;
374 850 : if (flags & (1u << 1)) if (tl_skip_bool(r) != 0) return -1;
375 850 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
376 850 : if (flags & (1u << 3)) if (tl_skip_notification_sound(r) != 0) return -1;
377 850 : if (flags & (1u << 4)) if (tl_skip_notification_sound(r) != 0) return -1;
378 850 : if (flags & (1u << 5)) if (tl_skip_notification_sound(r) != 0) return -1;
379 850 : if (flags & (1u << 6)) if (tl_skip_bool(r) != 0) return -1;
380 850 : if (flags & (1u << 7)) if (tl_skip_bool(r) != 0) return -1;
381 850 : if (flags & (1u << 8)) if (tl_skip_notification_sound(r) != 0) return -1;
382 850 : if (flags & (1u << 9)) if (tl_skip_notification_sound(r) != 0) return -1;
383 850 : if (flags & (1u << 10)) if (tl_skip_notification_sound(r) != 0) return -1;
384 850 : return 0;
385 : }
386 :
387 : /* draftMessageEmpty#1b0c841a flags:# date:flags.0?int
388 : * draftMessage#3fccf7ef — we do NOT fully parse this (it contains
389 : * InputReplyTo, Vector<MessageEntity>, InputMedia which are deep nested).
390 : * Return -1 so the caller stops iteration; callers wrapping dialogs can
391 : * skip entries with draft by first checking Dialog.flags.1.
392 : */
393 11 : int tl_skip_draft_message(TlReader *r) {
394 11 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
395 11 : uint32_t crc = tl_read_uint32(r);
396 11 : if (crc == CRC_draftMessageEmpty) {
397 5 : if (r->len - r->pos < 4) return -1;
398 5 : uint32_t flags = tl_read_uint32(r);
399 5 : if (flags & 1u) {
400 3 : if (r->len - r->pos < 4) return -1;
401 3 : tl_read_int32(r); /* date */
402 : }
403 5 : return 0;
404 : }
405 6 : if (crc == CRC_draftMessage) {
406 3 : logger_log(LOG_WARN,
407 : "tl_skip_draft_message: non-empty draft not parseable yet");
408 3 : return -1;
409 : }
410 3 : logger_log(LOG_WARN, "tl_skip_draft_message: unknown 0x%08x", crc);
411 3 : return -1;
412 : }
413 :
414 27 : int tl_skip_message_entity(TlReader *r) {
415 27 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
416 27 : uint32_t crc = tl_read_uint32(r);
417 :
418 : /* All entities start with offset:int length:int (8 bytes). */
419 27 : switch (crc) {
420 15 : case CRC_messageEntityUnknown:
421 : case CRC_messageEntityMention:
422 : case CRC_messageEntityHashtag:
423 : case CRC_messageEntityBotCommand:
424 : case CRC_messageEntityUrl:
425 : case CRC_messageEntityEmail:
426 : case CRC_messageEntityBold:
427 : case CRC_messageEntityItalic:
428 : case CRC_messageEntityCode:
429 : case CRC_messageEntityPhone:
430 : case CRC_messageEntityCashtag:
431 : case CRC_messageEntityUnderline:
432 : case CRC_messageEntityStrike:
433 : case CRC_messageEntityBankCard:
434 : case CRC_messageEntitySpoiler:
435 15 : if (r->len - r->pos < 8) return -1;
436 15 : tl_read_int32(r); tl_read_int32(r);
437 15 : return 0;
438 :
439 0 : case CRC_messageEntityPre:
440 0 : if (r->len - r->pos < 8) return -1;
441 0 : tl_read_int32(r); tl_read_int32(r);
442 0 : return tl_skip_string(r); /* language */
443 :
444 3 : case CRC_messageEntityTextUrl:
445 3 : if (r->len - r->pos < 8) return -1;
446 3 : tl_read_int32(r); tl_read_int32(r);
447 3 : return tl_skip_string(r); /* url */
448 :
449 2 : case CRC_messageEntityMentionName:
450 2 : if (r->len - r->pos < 16) return -1;
451 2 : tl_read_int32(r); tl_read_int32(r);
452 2 : tl_read_int64(r); /* user_id */
453 2 : return 0;
454 :
455 2 : case CRC_messageEntityCustomEmoji:
456 2 : if (r->len - r->pos < 16) return -1;
457 2 : tl_read_int32(r); tl_read_int32(r);
458 2 : tl_read_int64(r); /* document_id */
459 2 : return 0;
460 :
461 2 : case CRC_messageEntityBlockquote:
462 : /* flags:# offset:int length:int — no string payload */
463 2 : if (r->len - r->pos < 12) return -1;
464 2 : tl_read_uint32(r); /* flags */
465 2 : tl_read_int32(r); tl_read_int32(r);
466 2 : return 0;
467 :
468 3 : default:
469 3 : logger_log(LOG_WARN, "tl_skip_message_entity: unknown 0x%08x", crc);
470 3 : return -1;
471 : }
472 : }
473 :
474 47 : int tl_skip_message_entities_vector(TlReader *r) {
475 47 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
476 47 : uint32_t vec_crc = tl_read_uint32(r);
477 47 : if (vec_crc != TL_vector) {
478 1 : logger_log(LOG_WARN,
479 : "tl_skip_message_entities_vector: expected vector 0x%08x",
480 : vec_crc);
481 1 : return -1;
482 : }
483 46 : uint32_t count = tl_read_uint32(r);
484 68 : for (uint32_t i = 0; i < count; i++) {
485 24 : if (tl_skip_message_entity(r) != 0) return -1;
486 : }
487 44 : return 0;
488 : }
489 :
490 : /* ---- ReplyMarkup skipper ---- */
491 :
492 : /* Skip a KeyboardButton. Returns -1 on unknown variant. */
493 14 : static int skip_keyboard_button(TlReader *r) {
494 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
495 14 : uint32_t crc = tl_read_uint32(r);
496 14 : switch (crc) {
497 2 : case CRC_keyboardButton:
498 : case CRC_keyboardButtonRequestPhone:
499 : case CRC_keyboardButtonRequestGeoLoc:
500 : case CRC_keyboardButtonGame:
501 : case CRC_keyboardButtonBuy:
502 2 : return tl_skip_string(r); /* text */
503 4 : case CRC_keyboardButtonUrl: {
504 4 : if (tl_skip_string(r) != 0) return -1; /* text */
505 4 : return tl_skip_string(r); /* url */
506 : }
507 7 : case CRC_keyboardButtonCallback: {
508 7 : if (r->len - r->pos < 4) return -1;
509 7 : tl_read_uint32(r); /* flags */
510 7 : if (tl_skip_string(r) != 0) return -1; /* text */
511 7 : return tl_skip_string(r); /* data:bytes */
512 : }
513 0 : case CRC_keyboardButtonSwitchInline: {
514 0 : if (r->len - r->pos < 4) return -1;
515 0 : uint32_t flags = tl_read_uint32(r);
516 0 : if (tl_skip_string(r) != 0) return -1; /* text */
517 0 : if (tl_skip_string(r) != 0) return -1; /* query */
518 0 : if (flags & (1u << 1)) {
519 : /* peer_types:Vector<InlineQueryPeerType> — unknown nested
520 : * variants; bail. */
521 0 : logger_log(LOG_WARN,
522 : "skip_keyboard_button: peer_types on switchInline");
523 0 : return -1;
524 : }
525 0 : return 0;
526 : }
527 0 : case CRC_keyboardButtonUrlAuth: {
528 0 : if (r->len - r->pos < 4) return -1;
529 0 : uint32_t flags = tl_read_uint32(r);
530 0 : if (tl_skip_string(r) != 0) return -1; /* text */
531 0 : if (flags & (1u << 0))
532 0 : if (tl_skip_string(r) != 0) return -1;/* fwd_text */
533 0 : if (tl_skip_string(r) != 0) return -1; /* url */
534 0 : if (r->len - r->pos < 4) return -1;
535 0 : tl_read_int32(r); /* button_id */
536 0 : return 0;
537 : }
538 0 : case CRC_keyboardButtonRequestPoll: {
539 0 : if (r->len - r->pos < 4) return -1;
540 0 : uint32_t flags = tl_read_uint32(r);
541 0 : if (flags & (1u << 0)) {
542 0 : if (r->len - r->pos < 4) return -1;
543 0 : tl_read_uint32(r); /* Bool (crc only) */
544 : }
545 0 : return tl_skip_string(r); /* text */
546 : }
547 0 : case CRC_keyboardButtonUserProfile: {
548 0 : if (tl_skip_string(r) != 0) return -1; /* text */
549 0 : if (r->len - r->pos < 8) return -1;
550 0 : tl_read_int64(r); /* user_id */
551 0 : return 0;
552 : }
553 0 : case CRC_keyboardButtonWebView:
554 : case CRC_keyboardButtonSimpleWebView: {
555 0 : if (tl_skip_string(r) != 0) return -1; /* text */
556 0 : return tl_skip_string(r); /* url */
557 : }
558 1 : default:
559 1 : logger_log(LOG_WARN, "skip_keyboard_button: unknown 0x%08x", crc);
560 1 : return -1;
561 : }
562 : }
563 :
564 : /* Skip a KeyboardButtonRow = crc + Vector<KeyboardButton>. */
565 11 : static int skip_keyboard_button_row(TlReader *r) {
566 11 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
567 11 : uint32_t crc = tl_read_uint32(r);
568 11 : if (crc != CRC_keyboardButtonRow) {
569 0 : logger_log(LOG_WARN,
570 : "skip_keyboard_button_row: expected 0x77608b83 got 0x%08x",
571 : crc);
572 0 : return -1;
573 : }
574 11 : if (r->len - r->pos < 8) return -1;
575 11 : uint32_t vec_crc = tl_read_uint32(r);
576 11 : if (vec_crc != TL_vector) return -1;
577 11 : uint32_t n = tl_read_uint32(r);
578 24 : for (uint32_t i = 0; i < n; i++) {
579 14 : if (skip_keyboard_button(r) != 0) return -1;
580 : }
581 10 : return 0;
582 : }
583 :
584 : /* Skip a Vector<KeyboardButtonRow>. */
585 13 : static int skip_button_row_vector(TlReader *r) {
586 13 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
587 13 : uint32_t vec_crc = tl_read_uint32(r);
588 13 : if (vec_crc != TL_vector) return -1;
589 13 : uint32_t n = tl_read_uint32(r);
590 23 : for (uint32_t i = 0; i < n; i++) {
591 11 : if (skip_keyboard_button_row(r) != 0) return -1;
592 : }
593 12 : return 0;
594 : }
595 :
596 25 : int tl_skip_reply_markup(TlReader *r) {
597 25 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
598 24 : uint32_t crc = tl_read_uint32(r);
599 24 : switch (crc) {
600 6 : case CRC_replyKeyboardHide:
601 : case CRC_replyKeyboardForceReply: {
602 : /* Body: flags:# (+ optional placeholder:flags.3?string for
603 : * forceReply). */
604 6 : if (r->len - r->pos < 4) return -1;
605 6 : uint32_t flags = tl_read_uint32(r);
606 6 : if (crc == CRC_replyKeyboardForceReply && (flags & (1u << 3))) {
607 3 : if (tl_skip_string(r) != 0) return -1; /* placeholder */
608 : }
609 6 : return 0;
610 : }
611 0 : case CRC_replyKeyboardMarkup: {
612 0 : if (r->len - r->pos < 4) return -1;
613 0 : uint32_t flags = tl_read_uint32(r);
614 0 : if (skip_button_row_vector(r) != 0) return -1;
615 0 : if (flags & (1u << 3)) {
616 0 : if (tl_skip_string(r) != 0) return -1; /* placeholder */
617 : }
618 0 : return 0;
619 : }
620 13 : case CRC_replyInlineMarkup:
621 13 : return skip_button_row_vector(r);
622 5 : default:
623 5 : logger_log(LOG_WARN, "tl_skip_reply_markup: unknown 0x%08x", crc);
624 5 : return -1;
625 : }
626 : }
627 :
628 : /* ---- MessageReactions skipper ---- */
629 :
630 : /* Skip a Reaction variant. */
631 19 : static int skip_reaction(TlReader *r) {
632 19 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
633 19 : uint32_t crc = tl_read_uint32(r);
634 19 : switch (crc) {
635 4 : case CRC_reactionEmpty:
636 : case CRC_reactionPaid:
637 4 : return 0;
638 10 : case CRC_reactionEmoji:
639 10 : return tl_skip_string(r);
640 3 : case CRC_reactionCustomEmoji:
641 3 : if (r->len - r->pos < 8) return -1;
642 3 : tl_read_int64(r); /* document_id */
643 3 : return 0;
644 2 : default:
645 2 : logger_log(LOG_WARN, "skip_reaction: unknown 0x%08x", crc);
646 2 : return -1;
647 : }
648 : }
649 :
650 : /* Skip a ReactionCount#a3d1cb80: flags + (chosen_order) + Reaction + count. */
651 17 : static int skip_reaction_count(TlReader *r) {
652 17 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
653 17 : uint32_t crc = tl_read_uint32(r);
654 17 : if (crc != CRC_reactionCount) {
655 0 : logger_log(LOG_WARN, "skip_reaction_count: expected 0xa3d1cb80 got 0x%08x",
656 : crc);
657 0 : return -1;
658 : }
659 17 : if (r->len - r->pos < 4) return -1;
660 17 : uint32_t flags = tl_read_uint32(r);
661 17 : if (flags & (1u << 0)) {
662 3 : if (r->len - r->pos < 4) return -1;
663 3 : tl_read_int32(r); /* chosen_order */
664 : }
665 17 : if (skip_reaction(r) != 0) return -1;
666 15 : if (r->len - r->pos < 4) return -1;
667 15 : tl_read_int32(r); /* count */
668 15 : return 0;
669 : }
670 :
671 16 : int tl_skip_message_reactions(TlReader *r) {
672 16 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
673 16 : uint32_t crc = tl_read_uint32(r);
674 16 : if (crc != CRC_messageReactions) {
675 0 : logger_log(LOG_WARN, "tl_skip_message_reactions: unknown 0x%08x", crc);
676 0 : return -1;
677 : }
678 16 : if (r->len - r->pos < 4) return -1;
679 16 : uint32_t flags = tl_read_uint32(r);
680 : /* results:Vector<ReactionCount> — required. */
681 16 : if (r->len - r->pos < 8) return -1;
682 16 : uint32_t vec_crc = tl_read_uint32(r);
683 16 : if (vec_crc != TL_vector) return -1;
684 16 : uint32_t n = tl_read_uint32(r);
685 29 : for (uint32_t i = 0; i < n; i++) {
686 15 : if (skip_reaction_count(r) != 0) return -1;
687 : }
688 : /* recent_reactions:flags.1?Vector<MessagePeerReaction> — BAIL */
689 : /* top_reactors:flags.2?Vector<MessagePeerVote> — BAIL */
690 14 : if (flags & ((1u << 1) | (1u << 2))) {
691 3 : logger_log(LOG_WARN,
692 : "tl_skip_message_reactions: recent/top reactors present "
693 : "(flags=0x%08x) — not supported", flags);
694 3 : return -1;
695 : }
696 11 : return 0;
697 : }
698 :
699 : /* ---- MessageReplies skipper ----
700 : * messageReplies#83d60fc2 flags:# comments:flags.0?true
701 : * replies:int replies_pts:int
702 : * recent_repliers:flags.1?Vector<Peer>
703 : * channel_id:flags.0?long
704 : * max_id:flags.2?int read_max_id:flags.3?int
705 : */
706 16 : int tl_skip_message_replies(TlReader *r) {
707 16 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
708 16 : uint32_t crc = tl_read_uint32(r);
709 16 : if (crc != CRC_messageReplies) {
710 3 : logger_log(LOG_WARN, "tl_skip_message_replies: unknown 0x%08x", crc);
711 3 : return -1;
712 : }
713 13 : if (r->len - r->pos < 12) return -1;
714 13 : uint32_t flags = tl_read_uint32(r);
715 13 : tl_read_int32(r); /* replies */
716 13 : tl_read_int32(r); /* replies_pts */
717 13 : if (flags & (1u << 1)) {
718 3 : if (r->len - r->pos < 8) return -1;
719 3 : uint32_t vec_crc = tl_read_uint32(r);
720 3 : if (vec_crc != TL_vector) return -1;
721 3 : uint32_t n = tl_read_uint32(r);
722 7 : for (uint32_t i = 0; i < n; i++) {
723 4 : if (tl_skip_peer(r) != 0) return -1;
724 : }
725 : }
726 13 : if (flags & (1u << 0)) {
727 3 : if (r->len - r->pos < 8) return -1;
728 3 : tl_read_int64(r); /* channel_id */
729 : }
730 13 : if (flags & (1u << 2)) {
731 3 : if (r->len - r->pos < 4) return -1;
732 3 : tl_read_int32(r); /* max_id */
733 : }
734 13 : if (flags & (1u << 3)) {
735 2 : if (r->len - r->pos < 4) return -1;
736 2 : tl_read_int32(r); /* read_max_id */
737 : }
738 13 : return 0;
739 : }
740 :
741 : /* ---- FactCheck skipper ----
742 : * factCheck#b89bfccf flags:#
743 : * need_check:flags.0?true
744 : * country:flags.1?string
745 : * text:flags.1?TextWithEntities
746 : * hash:long
747 : *
748 : * textWithEntities#751f3146 text:string entities:Vector<MessageEntity>
749 : */
750 15 : int tl_skip_factcheck(TlReader *r) {
751 15 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
752 15 : uint32_t crc = tl_read_uint32(r);
753 15 : if (crc != CRC_factCheck) {
754 3 : logger_log(LOG_WARN, "tl_skip_factcheck: unknown 0x%08x", crc);
755 3 : return -1;
756 : }
757 12 : if (r->len - r->pos < 4) return -1;
758 12 : uint32_t flags = tl_read_uint32(r);
759 12 : if (flags & (1u << 1)) {
760 9 : if (tl_skip_string(r) != 0) return -1; /* country */
761 : /* text:TextWithEntities */
762 9 : if (r->len - r->pos < 4) return -1;
763 9 : uint32_t twe_crc = tl_read_uint32(r);
764 9 : if (twe_crc != CRC_textWithEntities) {
765 0 : logger_log(LOG_WARN,
766 : "tl_skip_factcheck: expected textWithEntities got 0x%08x",
767 : twe_crc);
768 0 : return -1;
769 : }
770 9 : if (tl_skip_string(r) != 0) return -1; /* text */
771 9 : if (tl_skip_message_entities_vector(r) != 0) return -1;
772 : }
773 12 : if (r->len - r->pos < 8) return -1;
774 12 : tl_read_int64(r); /* hash */
775 12 : return 0;
776 : }
777 :
778 : /* messageFwdHeader#4e4df4bb flags:#
779 : * imported:flags.7?true (no data)
780 : * saved_out:flags.11?true (no data)
781 : * from_id:flags.0?Peer
782 : * from_name:flags.5?string
783 : * date:int (always present)
784 : * channel_post:flags.2?int
785 : * post_author:flags.3?string
786 : * saved_from_peer:flags.4?Peer
787 : * saved_from_msg_id:flags.4?int
788 : * saved_from_id:flags.8?Peer
789 : * saved_from_name:flags.9?string
790 : * saved_date:flags.10?int
791 : * psa_type:flags.6?string
792 : */
793 13 : int tl_skip_message_fwd_header(TlReader *r) {
794 13 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
795 12 : uint32_t crc = tl_read_uint32(r);
796 12 : if (crc != CRC_messageFwdHeader) {
797 2 : logger_log(LOG_WARN,
798 : "tl_skip_message_fwd_header: unexpected 0x%08x", crc);
799 2 : return -1;
800 : }
801 10 : uint32_t flags = tl_read_uint32(r);
802 :
803 10 : if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
804 10 : if (flags & (1u << 5)) if (tl_skip_string(r) != 0) return -1;
805 10 : if (r->len - r->pos < 4) return -1;
806 10 : tl_read_int32(r); /* date */
807 10 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
808 10 : if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
809 10 : if (flags & (1u << 4)) {
810 0 : if (tl_skip_peer(r) != 0) return -1;
811 0 : if (r->len - r->pos < 4) return -1;
812 0 : tl_read_int32(r);
813 : }
814 10 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1;
815 10 : if (flags & (1u << 9)) if (tl_skip_string(r) != 0) return -1;
816 10 : if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
817 10 : if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
818 10 : return 0;
819 : }
820 :
821 : /* messageReplyHeader#afbc09db flags:#
822 : * reply_to_scheduled:flags.2?true (no data)
823 : * forum_topic:flags.3?true (no data)
824 : * quote:flags.9?true (no data)
825 : * reply_to_msg_id:flags.4?int
826 : * reply_to_peer_id:flags.0?Peer
827 : * reply_from:flags.5?MessageFwdHeader
828 : * reply_media:flags.8?MessageMedia <- NOT IMPLEMENTED
829 : * reply_to_top_id:flags.1?int
830 : * quote_text:flags.6?string
831 : * quote_entities:flags.7?Vector<MessageEntity>
832 : * quote_offset:flags.10?int
833 : */
834 17 : int tl_skip_message_reply_header(TlReader *r) {
835 17 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
836 17 : uint32_t crc = tl_read_uint32(r);
837 17 : if (crc == CRC_messageReplyStoryHeader) {
838 : /* peer_id:Peer story_id:int */
839 2 : if (tl_skip_peer(r) != 0) return -1;
840 2 : if (r->len - r->pos < 4) return -1;
841 2 : tl_read_int32(r);
842 2 : return 0;
843 : }
844 15 : if (crc != CRC_messageReplyHeader) {
845 3 : logger_log(LOG_WARN,
846 : "tl_skip_message_reply_header: unexpected 0x%08x", crc);
847 3 : return -1;
848 : }
849 12 : if (r->len - r->pos < 4) return -1;
850 12 : uint32_t flags = tl_read_uint32(r);
851 :
852 12 : if (flags & (1u << 4)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
853 12 : if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
854 12 : if (flags & (1u << 5)) if (tl_skip_message_fwd_header(r) != 0) return -1;
855 12 : if (flags & (1u << 8)) {
856 1 : logger_log(LOG_WARN,
857 : "tl_skip_message_reply_header: reply_media not implemented");
858 1 : return -1;
859 : }
860 11 : if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
861 11 : if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
862 11 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) return -1;
863 11 : if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
864 11 : return 0;
865 : }
866 :
867 : /* ---- PhotoSize skipper ----
868 : * Variants:
869 : * photoSizeEmpty#0e17e23c type:string
870 : * photoSize#75c78e60 type:string w:int h:int size:int
871 : * photoCachedSize#021e1ad6 type:string w:int h:int bytes:bytes
872 : * photoStrippedSize#e0b0bc2e type:string bytes:bytes
873 : * photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector<int>
874 : * photoPathSize#d8214d41 type:string bytes:bytes
875 : */
876 14 : int tl_skip_photo_size(TlReader *r) {
877 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
878 14 : uint32_t crc = tl_read_uint32(r);
879 14 : switch (crc) {
880 4 : case CRC_photoSizeEmpty:
881 4 : return tl_skip_string(r);
882 3 : case CRC_photoSize:
883 3 : if (tl_skip_string(r) != 0) return -1;
884 3 : if (r->len - r->pos < 12) return -1;
885 3 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
886 3 : return 0;
887 0 : case CRC_photoCachedSize:
888 0 : if (tl_skip_string(r) != 0) return -1;
889 0 : if (r->len - r->pos < 8) return -1;
890 0 : tl_read_int32(r); tl_read_int32(r);
891 0 : return tl_skip_string(r); /* bytes */
892 5 : case CRC_photoStrippedSize:
893 : case CRC_photoPathSize:
894 5 : if (tl_skip_string(r) != 0) return -1;
895 5 : return tl_skip_string(r); /* bytes */
896 0 : case CRC_photoSizeProgressive: {
897 0 : if (tl_skip_string(r) != 0) return -1;
898 0 : if (r->len - r->pos < 8) return -1;
899 0 : tl_read_int32(r); tl_read_int32(r);
900 : /* sizes:Vector<int> */
901 0 : if (r->len - r->pos < 8) return -1;
902 0 : uint32_t vec_crc = tl_read_uint32(r);
903 0 : if (vec_crc != TL_vector) return -1;
904 0 : uint32_t count = tl_read_uint32(r);
905 0 : if (r->len - r->pos < count * 4ULL) return -1;
906 0 : for (uint32_t i = 0; i < count; i++) tl_read_int32(r);
907 0 : return 0;
908 : }
909 2 : default:
910 2 : logger_log(LOG_WARN, "tl_skip_photo_size: unknown 0x%08x", crc);
911 2 : return -1;
912 : }
913 : }
914 :
915 3 : int tl_skip_photo_size_vector(TlReader *r) {
916 3 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
917 3 : uint32_t vec_crc = tl_read_uint32(r);
918 3 : if (vec_crc != TL_vector) return -1;
919 3 : uint32_t count = tl_read_uint32(r);
920 6 : for (uint32_t i = 0; i < count; i++) {
921 3 : if (tl_skip_photo_size(r) != 0) return -1;
922 : }
923 3 : return 0;
924 : }
925 :
926 : /* ---- Photo skipper / extractor ----
927 : * photoEmpty#2331b22d id:long
928 : * photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long
929 : * file_reference:bytes date:int
930 : * sizes:Vector<PhotoSize>
931 : * video_sizes:flags.1?Vector<VideoSize> <- BAIL
932 : * dc_id:int
933 : *
934 : * photo_full walks the full object and, when @p out is non-NULL, fills
935 : * access_hash, file_reference, dc_id and the largest PhotoSize.type.
936 : * Used by tl_skip_message_media_ex for MEDIA_PHOTO + file-download
937 : * support. tl_skip_photo is a thin wrapper that passes NULL.
938 : */
939 :
940 : /* Walk a Vector<PhotoSize> and record the `type` of the largest entry
941 : * (by pixel dimensions for photoSize / photoSizeProgressive, by byte
942 : * count as a tie-breaker for cached/stripped forms). Leaves the cursor
943 : * past the vector. */
944 7 : static int walk_photo_size_vector(TlReader *r,
945 : char *best_type, size_t best_type_cap) {
946 7 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
947 7 : uint32_t vec_crc = tl_read_uint32(r);
948 7 : if (vec_crc != TL_vector) return -1;
949 7 : uint32_t count = tl_read_uint32(r);
950 :
951 7 : long best_score = -1;
952 7 : if (best_type && best_type_cap) best_type[0] = '\0';
953 :
954 18 : for (uint32_t i = 0; i < count; i++) {
955 11 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
956 11 : uint32_t crc = tl_read_uint32(r);
957 :
958 : /* Capture type:string into a small buffer we may keep as best. */
959 11 : char type_buf[8] = {0};
960 11 : size_t type_n = 0;
961 : {
962 11 : size_t before = r->pos;
963 11 : char *s = tl_read_string(r);
964 11 : if (!s) return -1;
965 11 : type_n = strlen(s);
966 11 : if (type_n >= sizeof(type_buf)) type_n = sizeof(type_buf) - 1;
967 11 : memcpy(type_buf, s, type_n);
968 11 : type_buf[type_n] = '\0';
969 11 : free(s);
970 : (void)before;
971 : }
972 :
973 11 : long score = 0;
974 11 : switch (crc) {
975 0 : case CRC_photoSizeEmpty:
976 0 : score = -1; /* empty — never best */
977 0 : break;
978 7 : case CRC_photoSize: {
979 7 : if (r->len - r->pos < 12) return -1;
980 7 : int32_t w = tl_read_int32(r);
981 7 : int32_t h = tl_read_int32(r);
982 7 : int32_t sz = tl_read_int32(r); (void)sz;
983 7 : score = (long)w * (long)h;
984 7 : break;
985 : }
986 2 : case CRC_photoCachedSize: {
987 2 : if (r->len - r->pos < 8) return -1;
988 2 : int32_t w = tl_read_int32(r);
989 2 : int32_t h = tl_read_int32(r);
990 2 : if (tl_skip_string(r) != 0) return -1; /* bytes */
991 2 : score = (long)w * (long)h;
992 2 : break;
993 : }
994 0 : case CRC_photoStrippedSize:
995 : case CRC_photoPathSize:
996 0 : if (tl_skip_string(r) != 0) return -1; /* bytes */
997 0 : score = 0;
998 0 : break;
999 2 : case CRC_photoSizeProgressive: {
1000 2 : if (r->len - r->pos < 8) return -1;
1001 2 : int32_t w = tl_read_int32(r);
1002 2 : int32_t h = tl_read_int32(r);
1003 2 : if (r->len - r->pos < 8) return -1;
1004 2 : uint32_t vc = tl_read_uint32(r);
1005 2 : if (vc != TL_vector) return -1;
1006 2 : uint32_t nvals = tl_read_uint32(r);
1007 2 : if (r->len - r->pos < nvals * 4ULL) return -1;
1008 6 : for (uint32_t j = 0; j < nvals; j++) tl_read_int32(r);
1009 2 : score = (long)w * (long)h;
1010 2 : break;
1011 : }
1012 0 : default:
1013 0 : logger_log(LOG_WARN, "walk_photo_size_vector: unknown 0x%08x", crc);
1014 0 : return -1;
1015 : }
1016 :
1017 11 : if (score > best_score && best_type && best_type_cap) {
1018 4 : best_score = score;
1019 4 : size_t n = type_n < best_type_cap - 1 ? type_n : best_type_cap - 1;
1020 4 : memcpy(best_type, type_buf, n);
1021 4 : best_type[n] = '\0';
1022 : }
1023 : }
1024 7 : return 0;
1025 : }
1026 :
1027 21 : static int photo_full(TlReader *r, MediaInfo *out) {
1028 21 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1029 21 : uint32_t crc = tl_read_uint32(r);
1030 21 : if (crc == CRC_photoEmpty) {
1031 14 : if (r->len - r->pos < 8) return -1;
1032 14 : int64_t id = tl_read_int64(r);
1033 14 : if (out) out->photo_id = id;
1034 14 : return 0;
1035 : }
1036 7 : if (crc != CRC_photo) {
1037 0 : logger_log(LOG_WARN, "tl_skip_photo: unknown 0x%08x", crc);
1038 0 : return -1;
1039 : }
1040 7 : if (r->len - r->pos < 4) return -1;
1041 7 : uint32_t flags = tl_read_uint32(r);
1042 7 : if (r->len - r->pos < 16) return -1;
1043 7 : int64_t id = tl_read_int64(r);
1044 7 : int64_t access = tl_read_int64(r);
1045 7 : if (out) { out->photo_id = id; out->access_hash = access; }
1046 :
1047 : /* file_reference is a TL bytes field. Capture it into MediaInfo
1048 : * (truncating if it exceeds MEDIA_FILE_REF_MAX) while advancing. */
1049 7 : size_t fr_len = 0;
1050 7 : uint8_t *fr = tl_read_bytes(r, &fr_len);
1051 7 : if (!fr && fr_len != 0) return -1;
1052 7 : if (out) {
1053 4 : size_t n = fr_len;
1054 4 : if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
1055 4 : if (fr) memcpy(out->file_reference, fr, n);
1056 4 : out->file_reference_len = n;
1057 : }
1058 7 : free(fr);
1059 :
1060 7 : if (r->len - r->pos < 4) return -1;
1061 7 : tl_read_int32(r); /* date */
1062 :
1063 7 : if (walk_photo_size_vector(r,
1064 : out ? out->thumb_type : NULL,
1065 : out ? sizeof(out->thumb_type) : 0) != 0)
1066 0 : return -1;
1067 :
1068 7 : if (flags & (1u << 1)) {
1069 0 : logger_log(LOG_WARN, "tl_skip_photo: video_sizes not implemented");
1070 0 : return -1;
1071 : }
1072 7 : if (r->len - r->pos < 4) return -1;
1073 7 : int32_t dc = tl_read_int32(r);
1074 7 : if (out) out->dc_id = dc;
1075 7 : return 0;
1076 : }
1077 :
1078 12 : int tl_skip_photo(TlReader *r) {
1079 12 : return photo_full(r, NULL);
1080 : }
1081 :
1082 : /* ---- Document skipper ----
1083 : * documentEmpty#36f8c871 id:long
1084 : * document — flag-heavy; bail out without implementing for now. The layer-
1085 : * specific layout changes frequently, and ordinary chat messages that
1086 : * reach us typically have Photo, not Document. Callers treating a
1087 : * Document as complex+stop-iter is acceptable for v1.
1088 : */
1089 : /* DocumentAttribute CRCs we can skip without bailing. */
1090 : #define CRC_documentAttributeImageSize 0x6c37c15cu
1091 : #define CRC_documentAttributeAnimated 0x11b58939u
1092 : #define CRC_documentAttributeHasStickers 0x9801d2f7u
1093 : #define CRC_documentAttributeFilename 0x15590068u
1094 : #define CRC_documentAttributeVideo 0x43c57c48u
1095 : #define CRC_documentAttributeAudio 0x9852f9c6u
1096 : #define CRC_documentAttributeSticker 0x6319d612u
1097 : #define CRC_documentAttributeCustomEmoji 0xfd149899u
1098 :
1099 : /* InputStickerSet variants (documentAttributeSticker / CustomEmoji). */
1100 : #define CRC_inputStickerSetEmpty 0xffb62b95u
1101 : #define CRC_inputStickerSetID 0x9de7a269u
1102 : #define CRC_inputStickerSetShortName 0x861cc8a0u
1103 : #define CRC_inputStickerSetAnimatedEmoji 0x028703c8u
1104 : #define CRC_inputStickerSetDice 0xe67f520eu
1105 : #define CRC_inputStickerSetAnimatedEmojiAnimations 0x0cde3739u
1106 : #define CRC_inputStickerSetPremiumGifts 0xc88b3b02u
1107 : #define CRC_inputStickerSetEmojiGenericAnimations 0x04c4d4ceu
1108 : #define CRC_inputStickerSetEmojiDefaultStatuses 0x29d0f5eeu
1109 : #define CRC_inputStickerSetEmojiDefaultTopicIcons 0x44c1f8e9u
1110 : #define CRC_inputStickerSetEmojiChannelDefaultStatuses 0x49748553u
1111 :
1112 : /* MaskCoords (documentAttributeSticker.mask_coords). */
1113 : #define CRC_maskCoords 0xaed6dbb2u
1114 :
1115 : /* VideoSize variants (document.video_thumbs, flags.1). */
1116 : #define CRC_videoSize 0xde33b094u
1117 : #define CRC_videoSizeEmojiMarkup 0xf85c413cu
1118 : #define CRC_videoSizeStickerMarkup 0x0da082feu
1119 :
1120 : /* Skip an InputStickerSet variant. Used by documentAttributeSticker /
1121 : * CustomEmoji. Handles every declared variant; on unknown ones we bail. */
1122 5 : static int skip_input_sticker_set(TlReader *r) {
1123 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1124 5 : uint32_t crc = tl_read_uint32(r);
1125 5 : switch (crc) {
1126 3 : case CRC_inputStickerSetEmpty:
1127 : case CRC_inputStickerSetAnimatedEmoji:
1128 : case CRC_inputStickerSetAnimatedEmojiAnimations:
1129 : case CRC_inputStickerSetPremiumGifts:
1130 : case CRC_inputStickerSetEmojiGenericAnimations:
1131 : case CRC_inputStickerSetEmojiDefaultStatuses:
1132 : case CRC_inputStickerSetEmojiDefaultTopicIcons:
1133 : case CRC_inputStickerSetEmojiChannelDefaultStatuses:
1134 3 : return 0;
1135 1 : case CRC_inputStickerSetID:
1136 1 : if (r->len - r->pos < 16) return -1;
1137 1 : tl_read_int64(r); /* id */
1138 1 : tl_read_int64(r); /* access_hash */
1139 1 : return 0;
1140 1 : case CRC_inputStickerSetShortName:
1141 1 : return tl_skip_string(r);
1142 0 : case CRC_inputStickerSetDice:
1143 0 : return tl_skip_string(r); /* emoticon */
1144 0 : default:
1145 0 : logger_log(LOG_WARN,
1146 : "skip_input_sticker_set: unknown 0x%08x", crc);
1147 0 : return -1;
1148 : }
1149 : }
1150 :
1151 : /* Skip a MaskCoords#aed6dbb2: n:int + 3 doubles. */
1152 1 : static int skip_mask_coords(TlReader *r) {
1153 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1154 1 : uint32_t crc = tl_read_uint32(r);
1155 1 : if (crc != CRC_maskCoords) {
1156 0 : logger_log(LOG_WARN, "skip_mask_coords: unknown 0x%08x", crc);
1157 0 : return -1;
1158 : }
1159 1 : if (r->len - r->pos < 4 + 3 * 8) return -1;
1160 1 : tl_read_int32(r); /* n */
1161 1 : tl_read_double(r); tl_read_double(r); tl_read_double(r); /* x, y, zoom */
1162 1 : return 0;
1163 : }
1164 :
1165 : /* Skip a single VideoSize variant (document.video_thumbs element). */
1166 1 : static int skip_video_size(TlReader *r) {
1167 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1168 1 : uint32_t crc = tl_read_uint32(r);
1169 1 : switch (crc) {
1170 1 : case CRC_videoSize: {
1171 : /* flags:# type:string w:int h:int size:int video_start_ts:flags.0?double */
1172 1 : if (r->len - r->pos < 4) return -1;
1173 1 : uint32_t flags = tl_read_uint32(r);
1174 1 : if (tl_skip_string(r) != 0) return -1;
1175 1 : if (r->len - r->pos < 12) return -1;
1176 1 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
1177 1 : if (flags & (1u << 0)) {
1178 0 : if (r->len - r->pos < 8) return -1;
1179 0 : tl_read_double(r);
1180 : }
1181 1 : return 0;
1182 : }
1183 0 : case CRC_videoSizeEmojiMarkup: {
1184 0 : if (r->len - r->pos < 8) return -1;
1185 0 : tl_read_int64(r); /* emoji_id */
1186 : /* background_colors:Vector<int> */
1187 0 : if (r->len - r->pos < 8) return -1;
1188 0 : uint32_t vec = tl_read_uint32(r);
1189 0 : if (vec != TL_vector) return -1;
1190 0 : uint32_t n = tl_read_uint32(r);
1191 0 : if (r->len - r->pos < (size_t)n * 4) return -1;
1192 0 : for (uint32_t i = 0; i < n; i++) tl_read_int32(r);
1193 0 : return 0;
1194 : }
1195 0 : case CRC_videoSizeStickerMarkup: {
1196 0 : if (skip_input_sticker_set(r) != 0) return -1;
1197 0 : if (r->len - r->pos < 8) return -1;
1198 0 : tl_read_int64(r); /* sticker_id */
1199 0 : if (r->len - r->pos < 8) return -1;
1200 0 : uint32_t vec = tl_read_uint32(r);
1201 0 : if (vec != TL_vector) return -1;
1202 0 : uint32_t n = tl_read_uint32(r);
1203 0 : if (r->len - r->pos < (size_t)n * 4) return -1;
1204 0 : for (uint32_t i = 0; i < n; i++) tl_read_int32(r);
1205 0 : return 0;
1206 : }
1207 0 : default:
1208 0 : logger_log(LOG_WARN, "skip_video_size: unknown 0x%08x", crc);
1209 0 : return -1;
1210 : }
1211 : }
1212 :
1213 : /* Skip a Vector<VideoSize>. */
1214 1 : static int skip_video_size_vector(TlReader *r) {
1215 1 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1216 1 : uint32_t vec = tl_read_uint32(r);
1217 1 : if (vec != TL_vector) return -1;
1218 1 : uint32_t n = tl_read_uint32(r);
1219 2 : for (uint32_t i = 0; i < n; i++) {
1220 1 : if (skip_video_size(r) != 0) return -1;
1221 : }
1222 1 : return 0;
1223 : }
1224 :
1225 : /* Skip a single DocumentAttribute, optionally extracting the filename. */
1226 33 : static int skip_document_attribute(TlReader *r,
1227 : char *filename_out, size_t fn_cap) {
1228 33 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1229 33 : uint32_t crc = tl_read_uint32(r);
1230 33 : switch (crc) {
1231 2 : case CRC_documentAttributeImageSize:
1232 2 : if (r->len - r->pos < 8) return -1;
1233 2 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1234 2 : return 0;
1235 8 : case CRC_documentAttributeAnimated:
1236 : case CRC_documentAttributeHasStickers:
1237 8 : return 0;
1238 8 : case CRC_documentAttributeFilename: {
1239 8 : size_t before = r->pos;
1240 8 : char *s = tl_read_string(r);
1241 8 : if (!s) { r->pos = before; return -1; }
1242 8 : if (filename_out && fn_cap) {
1243 5 : size_t n = strlen(s);
1244 5 : if (n >= fn_cap) n = fn_cap - 1;
1245 5 : memcpy(filename_out, s, n);
1246 5 : filename_out[n] = '\0';
1247 : }
1248 8 : free(s);
1249 8 : return 0;
1250 : }
1251 4 : case CRC_documentAttributeVideo: {
1252 4 : if (r->len - r->pos < 4) return -1;
1253 4 : uint32_t flags = tl_read_uint32(r);
1254 4 : if (r->len - r->pos < 8 + 4 + 4) return -1;
1255 4 : tl_read_double(r); /* duration */
1256 4 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1257 4 : if (flags & (1u << 2)) {
1258 0 : if (r->len - r->pos < 4) return -1;
1259 0 : tl_read_int32(r); /* preload_prefix_size */
1260 : }
1261 4 : if (flags & (1u << 4)) {
1262 0 : if (r->len - r->pos < 8) return -1;
1263 0 : tl_read_double(r); /* video_start_ts */
1264 : }
1265 4 : if (flags & (1u << 5))
1266 0 : if (tl_skip_string(r) != 0) return -1; /* video_codec */
1267 4 : return 0;
1268 : }
1269 6 : case CRC_documentAttributeAudio: {
1270 6 : if (r->len - r->pos < 8) return -1;
1271 6 : uint32_t flags = tl_read_uint32(r);
1272 6 : tl_read_int32(r); /* duration */
1273 6 : if (flags & (1u << 0))
1274 0 : if (tl_skip_string(r) != 0) return -1; /* title */
1275 6 : if (flags & (1u << 1))
1276 0 : if (tl_skip_string(r) != 0) return -1; /* performer */
1277 6 : if (flags & (1u << 2))
1278 0 : if (tl_skip_string(r) != 0) return -1; /* waveform:bytes */
1279 6 : return 0;
1280 : }
1281 4 : case CRC_documentAttributeSticker: {
1282 : /* flags:# mask:flags.1?true alt:string stickerset:InputStickerSet
1283 : * mask_coords:flags.0?MaskCoords */
1284 4 : if (r->len - r->pos < 4) return -1;
1285 4 : uint32_t flags = tl_read_uint32(r);
1286 4 : if (tl_skip_string(r) != 0) return -1; /* alt */
1287 4 : if (skip_input_sticker_set(r) != 0) return -1;
1288 4 : if (flags & (1u << 0)) {
1289 1 : if (skip_mask_coords(r) != 0) return -1;
1290 : }
1291 4 : return 0;
1292 : }
1293 1 : case CRC_documentAttributeCustomEmoji: {
1294 : /* flags:# free:flags.0?true text_color:flags.1?true alt:string
1295 : * stickerset:InputStickerSet */
1296 1 : if (r->len - r->pos < 4) return -1;
1297 1 : (void)tl_read_uint32(r); /* flags */
1298 1 : if (tl_skip_string(r) != 0) return -1; /* alt */
1299 1 : return skip_input_sticker_set(r);
1300 : }
1301 0 : default:
1302 0 : logger_log(LOG_WARN, "skip_document_attribute: unknown 0x%08x", crc);
1303 0 : return -1;
1304 : }
1305 : }
1306 :
1307 : /* Walk a Document. When @p out is non-NULL, fills id + access_hash +
1308 : * file_reference + dc_id + size + mime_type + filename. Returns -1 on
1309 : * thumbs / video_thumbs (flags.0 / flags.1) or an unknown attribute. */
1310 32 : static int document_inner(TlReader *r, MediaInfo *out) {
1311 32 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1312 32 : uint32_t crc = tl_read_uint32(r);
1313 32 : if (crc == CRC_documentEmpty) {
1314 8 : if (r->len - r->pos < 8) return -1;
1315 8 : int64_t id = tl_read_int64(r);
1316 8 : if (out) out->document_id = id;
1317 8 : return 0;
1318 : }
1319 24 : if (crc != CRC_document) {
1320 0 : logger_log(LOG_WARN, "tl_skip_document: unknown 0x%08x", crc);
1321 0 : return -1;
1322 : }
1323 24 : if (r->len - r->pos < 4) return -1;
1324 24 : uint32_t flags = tl_read_uint32(r);
1325 24 : if (r->len - r->pos < 16) return -1;
1326 24 : int64_t id = tl_read_int64(r);
1327 24 : int64_t access = tl_read_int64(r);
1328 24 : if (out) { out->document_id = id; out->access_hash = access; }
1329 :
1330 24 : size_t fr_len = 0;
1331 24 : uint8_t *fr = tl_read_bytes(r, &fr_len);
1332 24 : if (!fr && fr_len != 0) return -1;
1333 24 : if (out) {
1334 20 : size_t n = fr_len;
1335 20 : if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
1336 20 : if (fr) memcpy(out->file_reference, fr, n);
1337 20 : out->file_reference_len = n;
1338 : }
1339 24 : free(fr);
1340 :
1341 24 : if (r->len - r->pos < 4) return -1;
1342 24 : tl_read_int32(r); /* date */
1343 :
1344 24 : size_t mime_before = r->pos;
1345 24 : char *mime = tl_read_string(r);
1346 24 : if (!mime) { r->pos = mime_before; return -1; }
1347 24 : if (out) {
1348 20 : size_t n = strlen(mime);
1349 20 : if (n >= sizeof(out->document_mime)) n = sizeof(out->document_mime) - 1;
1350 20 : memcpy(out->document_mime, mime, n);
1351 20 : out->document_mime[n] = '\0';
1352 : }
1353 24 : free(mime);
1354 :
1355 24 : if (r->len - r->pos < 8) return -1;
1356 24 : int64_t size = tl_read_int64(r);
1357 24 : if (out) out->document_size = size;
1358 :
1359 24 : if (flags & (1u << 0)) {
1360 1 : if (tl_skip_photo_size_vector(r) != 0) return -1;
1361 : }
1362 24 : if (flags & (1u << 1)) {
1363 1 : if (skip_video_size_vector(r) != 0) return -1;
1364 : }
1365 :
1366 24 : if (r->len - r->pos < 4) return -1;
1367 24 : int32_t dc = tl_read_int32(r);
1368 24 : if (out) out->dc_id = dc;
1369 :
1370 : /* attributes:Vector<DocumentAttribute> */
1371 24 : if (r->len - r->pos < 8) return -1;
1372 24 : uint32_t vec_crc = tl_read_uint32(r);
1373 24 : if (vec_crc != TL_vector) return -1;
1374 24 : uint32_t n_attrs = tl_read_uint32(r);
1375 54 : for (uint32_t i = 0; i < n_attrs; i++) {
1376 30 : if (skip_document_attribute(r,
1377 : out ? out->document_filename : NULL,
1378 : out ? sizeof(out->document_filename) : 0) != 0)
1379 0 : return -1;
1380 : }
1381 24 : return 0;
1382 : }
1383 :
1384 9 : int tl_skip_document(TlReader *r) {
1385 9 : return document_inner(r, NULL);
1386 : }
1387 :
1388 : /* Skip GeoPoint: empty → CRC-only; geoPoint → CRC + long lat + long long +
1389 : * long access_hash + flags:# + optional accuracy_radius:flags.0?int.
1390 : * Actually:
1391 : * geoPointEmpty#1117dd5f
1392 : * geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long
1393 : * accuracy_radius:flags.0?int
1394 : */
1395 19 : static int skip_geo_point(TlReader *r) {
1396 19 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1397 19 : uint32_t crc = tl_read_uint32(r);
1398 19 : if (crc == CRC_geoPointEmpty) return 0;
1399 5 : if (crc != CRC_geoPoint) {
1400 0 : logger_log(LOG_WARN, "skip_geo_point: unknown 0x%08x", crc);
1401 0 : return -1;
1402 : }
1403 5 : if (r->len - r->pos < 28) return -1;
1404 5 : uint32_t flags = tl_read_uint32(r);
1405 5 : tl_read_double(r); tl_read_double(r); tl_read_int64(r);
1406 5 : if (flags & 1u) {
1407 0 : if (r->len - r->pos < 4) return -1;
1408 0 : tl_read_int32(r);
1409 : }
1410 5 : return 0;
1411 : }
1412 :
1413 : /* Forward decl: skip_story_item is defined later in the file (next to
1414 : * the MessageMedia switch) and is re-used by webPageAttributeStory. */
1415 : static int skip_story_item(TlReader *r);
1416 :
1417 : /* Forward decls: PageBlock recursion (Cover → PageBlock, Details →
1418 : * Vector<PageBlock>, EmbedPost → Vector<PageBlock>, List item blocks,
1419 : * etc.) and reliance on skip_geo_point for pageBlockMap. */
1420 : static int skip_page_block(TlReader *r);
1421 : static int skip_geo_point(TlReader *r);
1422 :
1423 : /* RichText tree (used by cached_page's PageBlock variants). Recursive:
1424 : * wrap-type variants contain a nested RichText. textConcat is a vector. */
1425 138 : static int skip_rich_text(TlReader *r) {
1426 138 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1427 138 : uint32_t crc = tl_read_uint32(r);
1428 138 : switch (crc) {
1429 92 : case CRC_textEmpty:
1430 92 : return 0;
1431 18 : case CRC_textPlain:
1432 18 : return tl_skip_string(r);
1433 13 : case CRC_textBold:
1434 : case CRC_textItalic:
1435 : case CRC_textUnderline:
1436 : case CRC_textStrike:
1437 : case CRC_textFixed:
1438 : case CRC_textSubscript:
1439 : case CRC_textSuperscript:
1440 : case CRC_textMarked:
1441 13 : return skip_rich_text(r);
1442 2 : case CRC_textUrl: {
1443 2 : if (skip_rich_text(r) != 0) return -1;
1444 2 : if (tl_skip_string(r) != 0) return -1;
1445 2 : if (r->len - r->pos < 8) return -1;
1446 2 : tl_read_int64(r); /* webpage_id */
1447 2 : return 0;
1448 : }
1449 4 : case CRC_textEmail:
1450 : case CRC_textPhone: {
1451 4 : if (skip_rich_text(r) != 0) return -1;
1452 4 : return tl_skip_string(r);
1453 : }
1454 5 : case CRC_textConcat: {
1455 5 : if (r->len - r->pos < 8) return -1;
1456 5 : uint32_t vec = tl_read_uint32(r);
1457 5 : if (vec != TL_vector) return -1;
1458 5 : uint32_t n = tl_read_uint32(r);
1459 27 : for (uint32_t i = 0; i < n; i++) {
1460 22 : if (skip_rich_text(r) != 0) return -1;
1461 : }
1462 5 : return 0;
1463 : }
1464 2 : case CRC_textImage: {
1465 2 : if (r->len - r->pos < 16) return -1;
1466 2 : tl_read_int64(r); /* document_id */
1467 2 : tl_read_int32(r); /* w */
1468 2 : tl_read_int32(r); /* h */
1469 2 : return 0;
1470 : }
1471 2 : case CRC_textAnchor: {
1472 2 : if (skip_rich_text(r) != 0) return -1;
1473 2 : return tl_skip_string(r);
1474 : }
1475 0 : default:
1476 0 : logger_log(LOG_WARN, "skip_rich_text: unknown 0x%08x", crc);
1477 0 : return -1;
1478 : }
1479 : }
1480 :
1481 : /* Skip a pageCaption#6f747657 text:RichText credit:RichText. */
1482 24 : static int skip_page_caption(TlReader *r) {
1483 24 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1484 24 : uint32_t crc = tl_read_uint32(r);
1485 24 : if (crc != CRC_pageCaption) {
1486 0 : logger_log(LOG_WARN, "skip_page_caption: unknown 0x%08x", crc);
1487 0 : return -1;
1488 : }
1489 24 : if (skip_rich_text(r) != 0) return -1;
1490 24 : return skip_rich_text(r);
1491 : }
1492 :
1493 : /* Skip a Vector<PageBlock> — recursive into skip_page_block. */
1494 17 : static int skip_page_block_vector(TlReader *r) {
1495 17 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1496 17 : uint32_t vec = tl_read_uint32(r);
1497 17 : if (vec != TL_vector) return -1;
1498 17 : uint32_t n = tl_read_uint32(r);
1499 26 : for (uint32_t i = 0; i < n; i++) {
1500 9 : if (skip_page_block(r) != 0) return -1;
1501 : }
1502 17 : return 0;
1503 : }
1504 :
1505 : /* Skip a single PageListItem. */
1506 6 : static int skip_page_list_item(TlReader *r) {
1507 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1508 6 : uint32_t crc = tl_read_uint32(r);
1509 6 : switch (crc) {
1510 3 : case CRC_pageListItemText:
1511 3 : return skip_rich_text(r);
1512 3 : case CRC_pageListItemBlocks:
1513 3 : return skip_page_block_vector(r);
1514 0 : default:
1515 0 : logger_log(LOG_WARN, "skip_page_list_item: unknown 0x%08x", crc);
1516 0 : return -1;
1517 : }
1518 : }
1519 :
1520 5 : static int skip_page_list_ordered_item(TlReader *r) {
1521 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1522 5 : uint32_t crc = tl_read_uint32(r);
1523 5 : switch (crc) {
1524 3 : case CRC_pageListOrderedItemText:
1525 3 : if (tl_skip_string(r) != 0) return -1; /* num */
1526 3 : return skip_rich_text(r);
1527 2 : case CRC_pageListOrderedItemBlocks:
1528 2 : if (tl_skip_string(r) != 0) return -1; /* num */
1529 2 : return skip_page_block_vector(r);
1530 0 : default:
1531 0 : logger_log(LOG_WARN,
1532 : "skip_page_list_ordered_item: unknown 0x%08x", crc);
1533 0 : return -1;
1534 : }
1535 : }
1536 :
1537 : /* pageTableCell#34566b6a flags:# header/align/valign:flags
1538 : * text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int */
1539 3 : static int skip_page_table_cell(TlReader *r) {
1540 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1541 3 : uint32_t crc = tl_read_uint32(r);
1542 3 : if (crc != CRC_pageTableCell) {
1543 0 : logger_log(LOG_WARN, "skip_page_table_cell: unknown 0x%08x", crc);
1544 0 : return -1;
1545 : }
1546 3 : if (r->len - r->pos < 4) return -1;
1547 3 : uint32_t flags = tl_read_uint32(r);
1548 3 : if (flags & (1u << 7)) {
1549 3 : if (skip_rich_text(r) != 0) return -1;
1550 : }
1551 3 : if (flags & (1u << 1)) {
1552 2 : if (r->len - r->pos < 4) return -1;
1553 2 : tl_read_int32(r); /* colspan */
1554 : }
1555 3 : if (flags & (1u << 2)) {
1556 2 : if (r->len - r->pos < 4) return -1;
1557 2 : tl_read_int32(r); /* rowspan */
1558 : }
1559 3 : return 0;
1560 : }
1561 :
1562 : /* pageTableRow#e0c0c5e5 cells:Vector<PageTableCell>. */
1563 3 : static int skip_page_table_row(TlReader *r) {
1564 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1565 3 : uint32_t crc = tl_read_uint32(r);
1566 3 : if (crc != CRC_pageTableRow) {
1567 0 : logger_log(LOG_WARN, "skip_page_table_row: unknown 0x%08x", crc);
1568 0 : return -1;
1569 : }
1570 3 : if (r->len - r->pos < 8) return -1;
1571 3 : uint32_t vec = tl_read_uint32(r);
1572 3 : if (vec != TL_vector) return -1;
1573 3 : uint32_t n = tl_read_uint32(r);
1574 6 : for (uint32_t i = 0; i < n; i++) {
1575 3 : if (skip_page_table_cell(r) != 0) return -1;
1576 : }
1577 3 : return 0;
1578 : }
1579 :
1580 : /* pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long
1581 : * title:flags.0?string description:flags.1?string
1582 : * photo_id:flags.2?long author:flags.3?string
1583 : * published_date:flags.4?int */
1584 3 : static int skip_page_related_article(TlReader *r) {
1585 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1586 3 : uint32_t crc = tl_read_uint32(r);
1587 3 : if (crc != CRC_pageRelatedArticle) {
1588 0 : logger_log(LOG_WARN,
1589 : "skip_page_related_article: unknown 0x%08x", crc);
1590 0 : return -1;
1591 : }
1592 3 : if (r->len - r->pos < 4) return -1;
1593 3 : uint32_t flags = tl_read_uint32(r);
1594 3 : if (tl_skip_string(r) != 0) return -1; /* url */
1595 3 : if (r->len - r->pos < 8) return -1;
1596 3 : tl_read_int64(r); /* webpage_id */
1597 3 : if (flags & (1u << 0)) if (tl_skip_string(r) != 0) return -1;
1598 3 : if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
1599 3 : if (flags & (1u << 2)) {
1600 2 : if (r->len - r->pos < 8) return -1;
1601 2 : tl_read_int64(r); /* photo_id */
1602 : }
1603 3 : if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
1604 3 : if (flags & (1u << 4)) {
1605 2 : if (r->len - r->pos < 4) return -1;
1606 2 : tl_read_int32(r); /* published_date */
1607 : }
1608 3 : return 0;
1609 : }
1610 :
1611 : /* Skip a single PageBlock — full coverage of the PageBlock tree. */
1612 90 : static int skip_page_block(TlReader *r) {
1613 90 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1614 90 : uint32_t crc = tl_read_uint32(r);
1615 90 : switch (crc) {
1616 13 : case CRC_pageBlockUnsupported:
1617 : case CRC_pageBlockDivider:
1618 13 : return 0;
1619 15 : case CRC_pageBlockTitle:
1620 : case CRC_pageBlockSubtitle:
1621 : case CRC_pageBlockHeader:
1622 : case CRC_pageBlockSubheader:
1623 : case CRC_pageBlockKicker:
1624 : case CRC_pageBlockParagraph:
1625 : case CRC_pageBlockFooter:
1626 15 : return skip_rich_text(r);
1627 3 : case CRC_pageBlockAnchor:
1628 3 : return tl_skip_string(r);
1629 2 : case CRC_pageBlockPreformatted: {
1630 2 : if (skip_rich_text(r) != 0) return -1;
1631 2 : return tl_skip_string(r); /* language */
1632 : }
1633 2 : case CRC_pageBlockAuthorDate: {
1634 2 : if (skip_rich_text(r) != 0) return -1; /* author */
1635 2 : if (r->len - r->pos < 4) return -1;
1636 2 : tl_read_int32(r); /* published_date */
1637 2 : return 0;
1638 : }
1639 5 : case CRC_pageBlockBlockquote:
1640 : case CRC_pageBlockPullquote: {
1641 5 : if (skip_rich_text(r) != 0) return -1; /* text */
1642 5 : return skip_rich_text(r); /* caption */
1643 : }
1644 3 : case CRC_pageBlockPhoto: {
1645 3 : if (r->len - r->pos < 4) return -1;
1646 3 : uint32_t flags = tl_read_uint32(r);
1647 3 : if (r->len - r->pos < 8) return -1;
1648 3 : tl_read_int64(r); /* photo_id */
1649 3 : if (skip_page_caption(r) != 0) return -1;
1650 3 : if (flags & (1u << 0)) {
1651 0 : if (tl_skip_string(r) != 0) return -1; /* url */
1652 0 : if (r->len - r->pos < 8) return -1;
1653 0 : tl_read_int64(r); /* webpage_id */
1654 : }
1655 3 : return 0;
1656 : }
1657 3 : case CRC_pageBlockVideo: {
1658 3 : if (r->len - r->pos < 12) return -1;
1659 3 : tl_read_uint32(r); /* flags (autoplay/loop only) */
1660 3 : tl_read_int64(r); /* video_id */
1661 3 : return skip_page_caption(r);
1662 : }
1663 3 : case CRC_pageBlockAudio: {
1664 3 : if (r->len - r->pos < 8) return -1;
1665 3 : tl_read_int64(r); /* audio_id */
1666 3 : return skip_page_caption(r);
1667 : }
1668 3 : case CRC_pageBlockCover:
1669 3 : return skip_page_block(r);
1670 3 : case CRC_pageBlockChannel:
1671 3 : return tl_skip_chat(r);
1672 3 : case CRC_pageBlockMap: {
1673 3 : if (skip_geo_point(r) != 0) return -1;
1674 3 : if (r->len - r->pos < 12) return -1;
1675 3 : tl_read_int32(r); /* zoom */
1676 3 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1677 3 : return skip_page_caption(r);
1678 : }
1679 5 : case CRC_pageBlockList: {
1680 5 : if (r->len - r->pos < 8) return -1;
1681 5 : uint32_t vec = tl_read_uint32(r);
1682 5 : if (vec != TL_vector) return -1;
1683 5 : uint32_t n = tl_read_uint32(r);
1684 11 : for (uint32_t i = 0; i < n; i++) {
1685 6 : if (skip_page_list_item(r) != 0) return -1;
1686 : }
1687 5 : return 0;
1688 : }
1689 5 : case CRC_pageBlockOrderedList: {
1690 5 : if (r->len - r->pos < 8) return -1;
1691 5 : uint32_t vec = tl_read_uint32(r);
1692 5 : if (vec != TL_vector) return -1;
1693 5 : uint32_t n = tl_read_uint32(r);
1694 10 : for (uint32_t i = 0; i < n; i++) {
1695 5 : if (skip_page_list_ordered_item(r) != 0) return -1;
1696 : }
1697 5 : return 0;
1698 : }
1699 6 : case CRC_pageBlockCollage:
1700 : case CRC_pageBlockSlideshow: {
1701 6 : if (skip_page_block_vector(r) != 0) return -1;
1702 6 : return skip_page_caption(r);
1703 : }
1704 3 : case CRC_pageBlockDetails: {
1705 3 : if (r->len - r->pos < 4) return -1;
1706 3 : tl_read_uint32(r); /* flags (open only) */
1707 3 : if (skip_page_block_vector(r) != 0) return -1;
1708 3 : return skip_rich_text(r); /* title */
1709 : }
1710 3 : case CRC_pageBlockRelatedArticles: {
1711 3 : if (skip_rich_text(r) != 0) return -1; /* title */
1712 3 : if (r->len - r->pos < 8) return -1;
1713 3 : uint32_t vec = tl_read_uint32(r);
1714 3 : if (vec != TL_vector) return -1;
1715 3 : uint32_t n = tl_read_uint32(r);
1716 6 : for (uint32_t i = 0; i < n; i++) {
1717 3 : if (skip_page_related_article(r) != 0) return -1;
1718 : }
1719 3 : return 0;
1720 : }
1721 3 : case CRC_pageBlockTable: {
1722 3 : if (r->len - r->pos < 4) return -1;
1723 3 : tl_read_uint32(r); /* flags (bordered/striped) */
1724 3 : if (skip_rich_text(r) != 0) return -1; /* title */
1725 3 : if (r->len - r->pos < 8) return -1;
1726 3 : uint32_t vec = tl_read_uint32(r);
1727 3 : if (vec != TL_vector) return -1;
1728 3 : uint32_t n = tl_read_uint32(r);
1729 6 : for (uint32_t i = 0; i < n; i++) {
1730 3 : if (skip_page_table_row(r) != 0) return -1;
1731 : }
1732 3 : return 0;
1733 : }
1734 3 : case CRC_pageBlockEmbed: {
1735 3 : if (r->len - r->pos < 4) return -1;
1736 3 : uint32_t flags = tl_read_uint32(r);
1737 3 : if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
1738 3 : if (flags & (1u << 2)) if (tl_skip_string(r) != 0) return -1;
1739 3 : if (flags & (1u << 4)) {
1740 0 : if (r->len - r->pos < 8) return -1;
1741 0 : tl_read_int64(r); /* poster_photo_id */
1742 : }
1743 3 : if (flags & (1u << 5)) {
1744 0 : if (r->len - r->pos < 8) return -1;
1745 0 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1746 : }
1747 3 : return skip_page_caption(r);
1748 : }
1749 3 : case CRC_pageBlockEmbedPost: {
1750 3 : if (tl_skip_string(r) != 0) return -1; /* url */
1751 3 : if (r->len - r->pos < 16) return -1;
1752 3 : tl_read_int64(r); /* webpage_id */
1753 3 : tl_read_int64(r); /* author_photo_id */
1754 3 : if (tl_skip_string(r) != 0) return -1; /* author */
1755 3 : if (r->len - r->pos < 4) return -1;
1756 3 : tl_read_int32(r); /* date */
1757 3 : if (skip_page_block_vector(r) != 0) return -1;
1758 3 : return skip_page_caption(r);
1759 : }
1760 1 : default:
1761 1 : logger_log(LOG_WARN, "skip_page_block: unsupported 0x%08x", crc);
1762 1 : return -1;
1763 : }
1764 : }
1765 :
1766 : /* Skip a Page object (webPage.cached_page). Best-effort: simple
1767 : * Instant View article bodies iterate; anything that contains a
1768 : * complex PageBlock variant bails so the caller stops walking. */
1769 30 : static int skip_page(TlReader *r) {
1770 30 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1771 30 : uint32_t crc = tl_read_uint32(r);
1772 30 : if (crc != CRC_page) {
1773 0 : logger_log(LOG_WARN, "skip_page: unknown Page variant 0x%08x", crc);
1774 0 : return -1;
1775 : }
1776 30 : if (r->len - r->pos < 4) return -1;
1777 30 : uint32_t flags = tl_read_uint32(r);
1778 30 : if (tl_skip_string(r) != 0) return -1; /* url */
1779 :
1780 : /* blocks:Vector<PageBlock> */
1781 30 : if (r->len - r->pos < 8) return -1;
1782 30 : uint32_t vec = tl_read_uint32(r);
1783 30 : if (vec != TL_vector) return -1;
1784 30 : uint32_t n = tl_read_uint32(r);
1785 107 : for (uint32_t i = 0; i < n; i++) {
1786 78 : if (skip_page_block(r) != 0) return -1;
1787 : }
1788 :
1789 : /* photos:Vector<Photo> */
1790 29 : if (r->len - r->pos < 8) return -1;
1791 29 : vec = tl_read_uint32(r);
1792 29 : if (vec != TL_vector) return -1;
1793 29 : n = tl_read_uint32(r);
1794 29 : for (uint32_t i = 0; i < n; i++) {
1795 0 : if (tl_skip_photo(r) != 0) return -1;
1796 : }
1797 :
1798 : /* documents:Vector<Document> */
1799 29 : if (r->len - r->pos < 8) return -1;
1800 29 : vec = tl_read_uint32(r);
1801 29 : if (vec != TL_vector) return -1;
1802 29 : n = tl_read_uint32(r);
1803 29 : for (uint32_t i = 0; i < n; i++) {
1804 0 : if (tl_skip_document(r) != 0) return -1;
1805 : }
1806 :
1807 : /* views:flags.3?int */
1808 29 : if (flags & (1u << 3)) {
1809 0 : if (r->len - r->pos < 4) return -1;
1810 0 : tl_read_int32(r);
1811 : }
1812 29 : return 0;
1813 : }
1814 :
1815 : /* Skip a Vector<WebPageAttribute> (webPage.attributes, flags.12). Only
1816 : * the three known WebPageAttribute variants are supported; anything
1817 : * else makes the caller bail. */
1818 8 : static int skip_webpage_attributes_vector(TlReader *r) {
1819 8 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1820 8 : uint32_t vec = tl_read_uint32(r);
1821 8 : if (vec != TL_vector) return -1;
1822 8 : uint32_t n = tl_read_uint32(r);
1823 17 : for (uint32_t i = 0; i < n; i++) {
1824 10 : if (r->len - r->pos < 4) return -1;
1825 10 : uint32_t crc = tl_read_uint32(r);
1826 10 : switch (crc) {
1827 3 : case CRC_webPageAttributeTheme: {
1828 : /* flags:# documents:flags.0?Vector<Document>
1829 : * settings:flags.1?ThemeSettings
1830 : * v1 bails if settings is present — ThemeSettings is a
1831 : * fat nested object we don't otherwise walk. */
1832 3 : if (r->len - r->pos < 4) return -1;
1833 3 : uint32_t flags = tl_read_uint32(r);
1834 3 : if (flags & (1u << 0)) {
1835 1 : if (r->len - r->pos < 8) return -1;
1836 1 : uint32_t dvec = tl_read_uint32(r);
1837 1 : if (dvec != TL_vector) return -1;
1838 1 : uint32_t dn = tl_read_uint32(r);
1839 1 : for (uint32_t j = 0; j < dn; j++) {
1840 0 : if (tl_skip_document(r) != 0) return -1;
1841 : }
1842 : }
1843 3 : if (flags & (1u << 1)) {
1844 0 : logger_log(LOG_WARN,
1845 : "skip_webpage_attributes: theme settings not supported");
1846 0 : return -1;
1847 : }
1848 3 : break;
1849 : }
1850 2 : case CRC_webPageAttributeStory: {
1851 : /* flags:# peer:Peer id:int story:flags.0?StoryItem */
1852 2 : if (r->len - r->pos < 4) return -1;
1853 2 : uint32_t flags = tl_read_uint32(r);
1854 2 : if (tl_skip_peer(r) != 0) return -1;
1855 2 : if (r->len - r->pos < 4) return -1;
1856 2 : tl_read_int32(r); /* id */
1857 2 : if (flags & (1u << 0)) {
1858 1 : if (skip_story_item(r) != 0) return -1;
1859 : }
1860 2 : break;
1861 : }
1862 4 : case CRC_webPageAttributeStickerSet: {
1863 : /* flags:# emojis:flags.0?true text_color:flags.1?true
1864 : * stickers:Vector<Document> */
1865 4 : if (r->len - r->pos < 4) return -1;
1866 4 : (void)tl_read_uint32(r); /* flags */
1867 4 : if (r->len - r->pos < 8) return -1;
1868 4 : uint32_t svec = tl_read_uint32(r);
1869 4 : if (svec != TL_vector) return -1;
1870 4 : uint32_t sn = tl_read_uint32(r);
1871 6 : for (uint32_t j = 0; j < sn; j++) {
1872 2 : if (tl_skip_document(r) != 0) return -1;
1873 : }
1874 4 : break;
1875 : }
1876 1 : default:
1877 1 : logger_log(LOG_WARN,
1878 : "skip_webpage_attributes: unknown variant 0x%08x", crc);
1879 1 : return -1;
1880 : }
1881 : }
1882 7 : return 0;
1883 : }
1884 :
1885 : /* Skip a WebPage variant. */
1886 52 : static int skip_webpage(TlReader *r) {
1887 52 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1888 52 : uint32_t crc = tl_read_uint32(r);
1889 52 : switch (crc) {
1890 3 : case CRC_webPageEmpty: {
1891 3 : if (r->len - r->pos < 12) return -1;
1892 3 : uint32_t flags = tl_read_uint32(r);
1893 3 : tl_read_int64(r); /* id */
1894 3 : if (flags & (1u << 0))
1895 1 : if (tl_skip_string(r) != 0) return -1;
1896 3 : return 0;
1897 : }
1898 3 : case CRC_webPagePending: {
1899 3 : if (r->len - r->pos < 12) return -1;
1900 3 : uint32_t flags = tl_read_uint32(r);
1901 3 : tl_read_int64(r);
1902 3 : if (flags & (1u << 0))
1903 1 : if (tl_skip_string(r) != 0) return -1;
1904 3 : if (r->len - r->pos < 4) return -1;
1905 3 : tl_read_int32(r); /* date */
1906 3 : return 0;
1907 : }
1908 2 : case CRC_webPageNotModified: {
1909 2 : if (r->len - r->pos < 4) return -1;
1910 2 : uint32_t flags = tl_read_uint32(r);
1911 2 : if (flags & (1u << 0)) {
1912 2 : if (r->len - r->pos < 4) return -1;
1913 2 : tl_read_int32(r); /* cached_page_views */
1914 : }
1915 2 : return 0;
1916 : }
1917 42 : case CRC_webPage: {
1918 : /* webPage#e89c45b2 flags:# id:long url:string display_url:string
1919 : * hash:int type:flags.0?string site_name:flags.1?string
1920 : * title:flags.2?string description:flags.3?string
1921 : * photo:flags.4?Photo embed_url:flags.5?string
1922 : * embed_type:flags.5?string embed_width/height:flags.6?int
1923 : * duration:flags.7?int author:flags.8?string
1924 : * document:flags.9?Document cached_page:flags.10?Page
1925 : * attributes:flags.12?Vector<WebPageAttribute>
1926 : */
1927 42 : if (r->len - r->pos < 16) return -1;
1928 42 : uint32_t flags = tl_read_uint32(r);
1929 42 : tl_read_int64(r); /* id */
1930 42 : if (tl_skip_string(r) != 0) return -1; /* url */
1931 42 : if (tl_skip_string(r) != 0) return -1; /* display_url */
1932 42 : if (r->len - r->pos < 4) return -1;
1933 42 : tl_read_int32(r); /* hash */
1934 42 : if (flags & (1u << 0))
1935 2 : if (tl_skip_string(r) != 0) return -1;
1936 42 : if (flags & (1u << 1))
1937 3 : if (tl_skip_string(r) != 0) return -1;
1938 42 : if (flags & (1u << 2))
1939 3 : if (tl_skip_string(r) != 0) return -1;
1940 42 : if (flags & (1u << 3))
1941 2 : if (tl_skip_string(r) != 0) return -1;
1942 42 : if (flags & (1u << 4)) {
1943 0 : if (photo_full(r, NULL) != 0) return -1;
1944 : }
1945 42 : if (flags & (1u << 5)) {
1946 2 : if (tl_skip_string(r) != 0) return -1;
1947 2 : if (tl_skip_string(r) != 0) return -1;
1948 : }
1949 42 : if (flags & (1u << 6)) {
1950 2 : if (r->len - r->pos < 8) return -1;
1951 2 : tl_read_int32(r); tl_read_int32(r);
1952 : }
1953 42 : if (flags & (1u << 7)) {
1954 2 : if (r->len - r->pos < 4) return -1;
1955 2 : tl_read_int32(r);
1956 : }
1957 42 : if (flags & (1u << 8))
1958 2 : if (tl_skip_string(r) != 0) return -1;
1959 42 : if (flags & (1u << 9)) {
1960 : /* document:Document — reuse tl_skip_document. */
1961 0 : if (tl_skip_document(r) != 0) return -1;
1962 : }
1963 42 : if (flags & (1u << 10)) {
1964 30 : if (skip_page(r) != 0) return -1;
1965 : }
1966 41 : if (flags & (1u << 12)) {
1967 8 : if (skip_webpage_attributes_vector(r) != 0) return -1;
1968 : }
1969 40 : return 0;
1970 : }
1971 2 : default:
1972 2 : logger_log(LOG_WARN, "skip_webpage: unknown 0x%08x", crc);
1973 2 : return -1;
1974 : }
1975 : }
1976 :
1977 : /* Skip a textWithEntities#751f3146: text:string + Vector<MessageEntity>. */
1978 19 : static int skip_text_with_entities(TlReader *r) {
1979 19 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1980 19 : uint32_t crc = tl_read_uint32(r);
1981 19 : if (crc != CRC_textWithEntities_poll) {
1982 0 : logger_log(LOG_WARN, "skip_text_with_entities: unknown 0x%08x", crc);
1983 0 : return -1;
1984 : }
1985 19 : if (tl_skip_string(r) != 0) return -1;
1986 19 : return tl_skip_message_entities_vector(r);
1987 : }
1988 :
1989 : /* Skip a Poll#58747131 object. */
1990 10 : static int skip_poll(TlReader *r) {
1991 10 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1992 10 : uint32_t crc = tl_read_uint32(r);
1993 10 : if (crc != CRC_poll) {
1994 0 : logger_log(LOG_WARN, "skip_poll: unknown 0x%08x", crc);
1995 0 : return -1;
1996 : }
1997 10 : if (r->len - r->pos < 12) return -1;
1998 10 : uint32_t flags = tl_read_uint32(r);
1999 10 : tl_read_int64(r); /* id */
2000 10 : if (skip_text_with_entities(r) != 0) return -1; /* question */
2001 :
2002 : /* answers:Vector<PollAnswer> */
2003 10 : if (r->len - r->pos < 8) return -1;
2004 10 : uint32_t vec_crc = tl_read_uint32(r);
2005 10 : if (vec_crc != TL_vector) return -1;
2006 10 : uint32_t n_answers = tl_read_uint32(r);
2007 19 : for (uint32_t i = 0; i < n_answers; i++) {
2008 9 : if (r->len - r->pos < 4) return -1;
2009 9 : uint32_t pa_crc = tl_read_uint32(r);
2010 9 : if (pa_crc != CRC_pollAnswer) {
2011 0 : logger_log(LOG_WARN, "skip_poll: bad PollAnswer 0x%08x", pa_crc);
2012 0 : return -1;
2013 : }
2014 9 : if (skip_text_with_entities(r) != 0) return -1;
2015 9 : if (tl_skip_string(r) != 0) return -1; /* option:bytes */
2016 : }
2017 10 : if (flags & (1u << 4)) {
2018 0 : if (r->len - r->pos < 4) return -1;
2019 0 : tl_read_int32(r); /* close_period */
2020 : }
2021 10 : if (flags & (1u << 5)) {
2022 1 : if (r->len - r->pos < 4) return -1;
2023 1 : tl_read_int32(r); /* close_date */
2024 : }
2025 10 : return 0;
2026 : }
2027 :
2028 : /* Skip a PollAnswerVoters#3b6ddad2. */
2029 7 : static int skip_poll_answer_voters(TlReader *r) {
2030 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2031 7 : uint32_t crc = tl_read_uint32(r);
2032 7 : if (crc != CRC_pollAnswerVoters) {
2033 2 : logger_log(LOG_WARN, "skip_poll_answer_voters: 0x%08x", crc);
2034 2 : return -1;
2035 : }
2036 5 : if (r->len - r->pos < 4) return -1;
2037 5 : tl_read_uint32(r); /* flags */
2038 5 : if (tl_skip_string(r) != 0) return -1; /* option:bytes */
2039 5 : if (r->len - r->pos < 4) return -1;
2040 5 : tl_read_int32(r); /* voters */
2041 5 : return 0;
2042 : }
2043 :
2044 : /* Skip a PollResults#7adc669d object. */
2045 10 : static int skip_poll_results(TlReader *r) {
2046 10 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2047 10 : uint32_t crc = tl_read_uint32(r);
2048 10 : if (crc != CRC_pollResults) {
2049 0 : logger_log(LOG_WARN, "skip_poll_results: 0x%08x", crc);
2050 0 : return -1;
2051 : }
2052 10 : if (r->len - r->pos < 4) return -1;
2053 10 : uint32_t flags = tl_read_uint32(r);
2054 10 : if (flags & (1u << 1)) {
2055 5 : if (r->len - r->pos < 8) return -1;
2056 5 : uint32_t vc = tl_read_uint32(r);
2057 5 : if (vc != TL_vector) return -1;
2058 5 : uint32_t n = tl_read_uint32(r);
2059 10 : for (uint32_t i = 0; i < n; i++) {
2060 7 : if (skip_poll_answer_voters(r) != 0) return -1;
2061 : }
2062 : }
2063 8 : if (flags & (1u << 2)) {
2064 3 : if (r->len - r->pos < 4) return -1;
2065 3 : tl_read_int32(r); /* total_voters */
2066 : }
2067 8 : if (flags & (1u << 3)) {
2068 3 : if (r->len - r->pos < 8) return -1;
2069 3 : uint32_t vc = tl_read_uint32(r);
2070 3 : if (vc != TL_vector) return -1;
2071 3 : uint32_t n = tl_read_uint32(r);
2072 6 : for (uint32_t i = 0; i < n; i++) {
2073 3 : if (tl_skip_peer(r) != 0) return -1;
2074 : }
2075 : }
2076 8 : if (flags & (1u << 4)) {
2077 3 : if (tl_skip_string(r) != 0) return -1; /* solution */
2078 3 : if (tl_skip_message_entities_vector(r) != 0) return -1;
2079 : }
2080 8 : return 0;
2081 : }
2082 :
2083 : /* ---- Game ----
2084 : * game#bdf9653b flags:# id:long access_hash:long short_name:string
2085 : * title:string description:string photo:Photo document:flags.0?Document
2086 : */
2087 9 : static int skip_game(TlReader *r) {
2088 9 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2089 9 : uint32_t crc = tl_read_uint32(r);
2090 9 : if (crc != CRC_game) {
2091 3 : logger_log(LOG_WARN, "skip_game: unexpected 0x%08x", crc);
2092 3 : return -1;
2093 : }
2094 6 : if (r->len - r->pos < 4 + 8 + 8) return -1;
2095 6 : uint32_t flags = tl_read_uint32(r);
2096 6 : tl_read_int64(r); /* id */
2097 6 : tl_read_int64(r); /* access_hash */
2098 6 : if (tl_skip_string(r) != 0) return -1; /* short_name */
2099 6 : if (tl_skip_string(r) != 0) return -1; /* title */
2100 6 : if (tl_skip_string(r) != 0) return -1; /* description */
2101 6 : if (tl_skip_photo(r) != 0) return -1; /* photo */
2102 6 : if (flags & (1u << 0)) {
2103 3 : if (tl_skip_document(r) != 0) return -1; /* document */
2104 : }
2105 6 : return 0;
2106 : }
2107 :
2108 : /* ---- MessageExtendedMedia ----
2109 : * messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int
2110 : * thumb:flags.1?PhotoSize video_duration:flags.2?int
2111 : * messageExtendedMedia#ee479c64 media:MessageMedia
2112 : */
2113 14 : static int skip_message_extended_media(TlReader *r) {
2114 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2115 14 : uint32_t crc = tl_read_uint32(r);
2116 14 : switch (crc) {
2117 8 : case CRC_messageExtendedMediaPreview: {
2118 8 : if (r->len - r->pos < 4) return -1;
2119 8 : uint32_t flags = tl_read_uint32(r);
2120 8 : if (flags & (1u << 0)) {
2121 3 : if (r->len - r->pos < 8) return -1;
2122 3 : tl_read_int32(r); tl_read_int32(r); /* w, h */
2123 : }
2124 8 : if (flags & (1u << 1)) {
2125 3 : if (tl_skip_photo_size(r) != 0) return -1;
2126 : }
2127 8 : if (flags & (1u << 2)) {
2128 3 : if (r->len - r->pos < 4) return -1;
2129 3 : tl_read_int32(r); /* video_duration */
2130 : }
2131 8 : return 0;
2132 : }
2133 3 : case CRC_messageExtendedMedia:
2134 : /* Recurse into the wrapped MessageMedia. The inner metadata is
2135 : * discarded — paid-media iteration only needs the outer kind. */
2136 3 : return tl_skip_message_media_ex(r, NULL);
2137 3 : default:
2138 3 : logger_log(LOG_WARN,
2139 : "skip_message_extended_media: unknown 0x%08x", crc);
2140 3 : return -1;
2141 : }
2142 : }
2143 :
2144 : /* ---- WebDocument ----
2145 : * webDocument#1c570ed1 url:string access_hash:long size:int
2146 : * mime_type:string attributes:Vector<DocumentAttribute>
2147 : * webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string
2148 : * attributes:Vector<DocumentAttribute>
2149 : */
2150 9 : static int skip_web_document(TlReader *r) {
2151 9 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2152 8 : uint32_t crc = tl_read_uint32(r);
2153 : int has_access_hash;
2154 8 : switch (crc) {
2155 3 : case CRC_webDocument: has_access_hash = 1; break;
2156 3 : case CRC_webDocumentNoProxy: has_access_hash = 0; break;
2157 2 : default:
2158 2 : logger_log(LOG_WARN, "skip_web_document: unknown 0x%08x", crc);
2159 2 : return -1;
2160 : }
2161 6 : if (tl_skip_string(r) != 0) return -1; /* url */
2162 6 : if (has_access_hash) {
2163 3 : if (r->len - r->pos < 8) return -1;
2164 3 : tl_read_int64(r); /* access_hash */
2165 : }
2166 6 : if (r->len - r->pos < 4) return -1;
2167 6 : tl_read_int32(r); /* size */
2168 6 : if (tl_skip_string(r) != 0) return -1; /* mime_type */
2169 : /* attributes:Vector<DocumentAttribute> */
2170 6 : if (r->len - r->pos < 8) return -1;
2171 6 : uint32_t vc = tl_read_uint32(r);
2172 6 : if (vc != TL_vector) return -1;
2173 6 : uint32_t n = tl_read_uint32(r);
2174 9 : for (uint32_t i = 0; i < n; i++) {
2175 3 : if (skip_document_attribute(r, NULL, 0) != 0) return -1;
2176 : }
2177 6 : return 0;
2178 : }
2179 :
2180 : /* ---- StoryFwdHeader ----
2181 : * storyFwdHeader#b826e150 flags:# modified:flags.3?true
2182 : * from:flags.0?Peer from_name:flags.1?string story_id:flags.2?int
2183 : */
2184 4 : static int skip_story_fwd_header(TlReader *r) {
2185 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2186 4 : uint32_t crc = tl_read_uint32(r);
2187 4 : if (crc != CRC_storyFwdHeader) {
2188 0 : logger_log(LOG_WARN, "skip_story_fwd_header: unknown 0x%08x", crc);
2189 0 : return -1;
2190 : }
2191 4 : if (r->len - r->pos < 4) return -1;
2192 4 : uint32_t flags = tl_read_uint32(r);
2193 4 : if (flags & (1u << 0))
2194 2 : if (tl_skip_peer(r) != 0) return -1; /* from */
2195 4 : if (flags & (1u << 1))
2196 2 : if (tl_skip_string(r) != 0) return -1; /* from_name */
2197 4 : if (flags & (1u << 2)) {
2198 4 : if (r->len - r->pos < 4) return -1;
2199 4 : tl_read_int32(r); /* story_id */
2200 : }
2201 4 : return 0;
2202 : }
2203 :
2204 : /* ---- MediaAreaCoordinates ----
2205 : * mediaAreaCoordinates#03d1ea4e flags:# x:double y:double w:double h:double
2206 : * rotation:double radius:flags.0?double
2207 : */
2208 15 : static int skip_media_area_coordinates(TlReader *r) {
2209 15 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2210 15 : uint32_t crc = tl_read_uint32(r);
2211 15 : if (crc != CRC_mediaAreaCoordinates) {
2212 0 : logger_log(LOG_WARN,
2213 : "skip_media_area_coordinates: unknown 0x%08x", crc);
2214 0 : return -1;
2215 : }
2216 15 : if (r->len - r->pos < 4 + 5 * 8) return -1;
2217 15 : uint32_t flags = tl_read_uint32(r);
2218 90 : for (int i = 0; i < 5; i++) tl_read_double(r); /* x, y, w, h, rot */
2219 15 : if (flags & (1u << 0)) {
2220 0 : if (r->len - r->pos < 8) return -1;
2221 0 : tl_read_double(r); /* radius */
2222 : }
2223 15 : return 0;
2224 : }
2225 :
2226 : /* ---- GeoPointAddress ----
2227 : * geoPointAddress#de4c5d93 flags:# country_iso2:string
2228 : * state:flags.0?string city:flags.1?string street:flags.2?string
2229 : */
2230 2 : static int skip_geo_point_address(TlReader *r) {
2231 2 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2232 2 : uint32_t crc = tl_read_uint32(r);
2233 2 : if (crc != CRC_geoPointAddress) {
2234 0 : logger_log(LOG_WARN,
2235 : "skip_geo_point_address: unknown 0x%08x", crc);
2236 0 : return -1;
2237 : }
2238 2 : if (r->len - r->pos < 4) return -1;
2239 2 : uint32_t flags = tl_read_uint32(r);
2240 2 : if (tl_skip_string(r) != 0) return -1; /* country_iso2 */
2241 2 : if (flags & (1u << 0))
2242 2 : if (tl_skip_string(r) != 0) return -1; /* state */
2243 2 : if (flags & (1u << 1))
2244 2 : if (tl_skip_string(r) != 0) return -1; /* city */
2245 2 : if (flags & (1u << 2))
2246 2 : if (tl_skip_string(r) != 0) return -1; /* street */
2247 2 : return 0;
2248 : }
2249 :
2250 : /* ---- MediaArea ---- dispatch over 7 known variants. */
2251 17 : static int skip_media_area(TlReader *r) {
2252 17 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2253 17 : uint32_t crc = tl_read_uint32(r);
2254 17 : switch (crc) {
2255 2 : case CRC_mediaAreaVenue:
2256 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2257 2 : if (skip_geo_point(r) != 0) return -1;
2258 2 : if (tl_skip_string(r) != 0) return -1; /* title */
2259 2 : if (tl_skip_string(r) != 0) return -1; /* address */
2260 2 : if (tl_skip_string(r) != 0) return -1; /* provider */
2261 2 : if (tl_skip_string(r) != 0) return -1; /* venue_id */
2262 2 : if (tl_skip_string(r) != 0) return -1; /* venue_type */
2263 2 : return 0;
2264 2 : case CRC_mediaAreaGeoPoint: {
2265 2 : if (r->len - r->pos < 4) return -1;
2266 2 : uint32_t flags = tl_read_uint32(r);
2267 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2268 2 : if (skip_geo_point(r) != 0) return -1;
2269 2 : if (flags & (1u << 0))
2270 2 : if (skip_geo_point_address(r) != 0) return -1;
2271 2 : return 0;
2272 : }
2273 2 : case CRC_mediaAreaSuggestedReaction: {
2274 2 : if (r->len - r->pos < 4) return -1;
2275 2 : tl_read_uint32(r); /* flags */
2276 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2277 2 : return skip_reaction(r);
2278 : }
2279 2 : case CRC_mediaAreaChannelPost:
2280 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2281 2 : if (r->len - r->pos < 12) return -1;
2282 2 : tl_read_int64(r); /* channel_id */
2283 2 : tl_read_int32(r); /* msg_id */
2284 2 : return 0;
2285 3 : case CRC_mediaAreaUrl:
2286 3 : if (skip_media_area_coordinates(r) != 0) return -1;
2287 3 : return tl_skip_string(r); /* url */
2288 2 : case CRC_mediaAreaWeather:
2289 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2290 2 : if (tl_skip_string(r) != 0) return -1; /* emoji */
2291 2 : if (r->len - r->pos < 8 + 4) return -1;
2292 2 : tl_read_double(r); /* temperature_c */
2293 2 : tl_read_int32(r); /* color */
2294 2 : return 0;
2295 2 : case CRC_mediaAreaStarGift:
2296 2 : if (skip_media_area_coordinates(r) != 0) return -1;
2297 2 : return tl_skip_string(r); /* slug */
2298 2 : default:
2299 2 : logger_log(LOG_WARN, "skip_media_area: unknown 0x%08x", crc);
2300 2 : return -1;
2301 : }
2302 : }
2303 :
2304 : /* ---- PrivacyRule ---- 12 variants; most are CRC-only. */
2305 8 : static int skip_privacy_rule(TlReader *r) {
2306 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2307 8 : uint32_t crc = tl_read_uint32(r);
2308 8 : switch (crc) {
2309 3 : case CRC_privacyValueAllowContacts:
2310 : case CRC_privacyValueAllowAll:
2311 : case CRC_privacyValueDisallowContacts:
2312 : case CRC_privacyValueDisallowAll:
2313 : case CRC_privacyValueAllowCloseFriends:
2314 : case CRC_privacyValueAllowPremium:
2315 : case CRC_privacyValueAllowBots:
2316 : case CRC_privacyValueDisallowBots:
2317 3 : return 0;
2318 3 : case CRC_privacyValueAllowUsers:
2319 : case CRC_privacyValueDisallowUsers:
2320 : case CRC_privacyValueAllowChatParticipants:
2321 : case CRC_privacyValueDisallowChatParticipants: {
2322 : /* Vector<long> */
2323 3 : if (r->len - r->pos < 8) return -1;
2324 3 : uint32_t vc = tl_read_uint32(r);
2325 3 : if (vc != TL_vector) return -1;
2326 3 : uint32_t n = tl_read_uint32(r);
2327 3 : if (r->len - r->pos < (size_t)n * 8) return -1;
2328 9 : for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
2329 3 : return 0;
2330 : }
2331 2 : default:
2332 2 : logger_log(LOG_WARN, "skip_privacy_rule: unknown 0x%08x", crc);
2333 2 : return -1;
2334 : }
2335 : }
2336 :
2337 : /* ---- StoryViews ----
2338 : * storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int
2339 : * forwards_count:flags.2?int reactions:flags.3?Vector<ReactionCount>
2340 : * reactions_count:flags.4?int recent_viewers:flags.0?Vector<long>
2341 : */
2342 5 : static int skip_story_views(TlReader *r) {
2343 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2344 5 : uint32_t crc = tl_read_uint32(r);
2345 5 : if (crc != CRC_storyViews) {
2346 2 : logger_log(LOG_WARN, "skip_story_views: unknown 0x%08x", crc);
2347 2 : return -1;
2348 : }
2349 3 : if (r->len - r->pos < 4) return -1;
2350 3 : uint32_t flags = tl_read_uint32(r);
2351 3 : if (r->len - r->pos < 4) return -1;
2352 3 : tl_read_int32(r); /* views_count */
2353 3 : if (flags & (1u << 2)) {
2354 2 : if (r->len - r->pos < 4) return -1;
2355 2 : tl_read_int32(r); /* forwards_count */
2356 : }
2357 3 : if (flags & (1u << 3)) {
2358 2 : if (r->len - r->pos < 8) return -1;
2359 2 : uint32_t vc = tl_read_uint32(r);
2360 2 : if (vc != TL_vector) return -1;
2361 2 : uint32_t n = tl_read_uint32(r);
2362 4 : for (uint32_t i = 0; i < n; i++)
2363 2 : if (skip_reaction_count(r) != 0) return -1;
2364 : }
2365 3 : if (flags & (1u << 4)) {
2366 2 : if (r->len - r->pos < 4) return -1;
2367 2 : tl_read_int32(r); /* reactions_count */
2368 : }
2369 3 : if (flags & (1u << 0)) {
2370 2 : if (r->len - r->pos < 8) return -1;
2371 2 : uint32_t vc = tl_read_uint32(r);
2372 2 : if (vc != TL_vector) return -1;
2373 2 : uint32_t n = tl_read_uint32(r);
2374 2 : if (r->len - r->pos < (size_t)n * 8) return -1;
2375 4 : for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
2376 : }
2377 3 : return 0;
2378 : }
2379 :
2380 : /* ---- StoryItem ----
2381 : * storyItemDeleted#51e6ee4f id:int
2382 : * storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true
2383 : * id:int date:int expire_date:int
2384 : * storyItem#79b26a24 — full layout. The inner `media:MessageMedia`
2385 : * dispatches recursively through tl_skip_message_media_ex, which
2386 : * handles the common sub-cases (empty / photo / document / webpage /
2387 : * …). Deeply nested stories-inside-stories would bail by the inner
2388 : * dispatcher's own bail rules.
2389 : */
2390 32 : static int skip_story_item(TlReader *r) {
2391 32 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2392 32 : uint32_t crc = tl_read_uint32(r);
2393 32 : switch (crc) {
2394 4 : case CRC_storyItemDeleted:
2395 4 : if (r->len - r->pos < 4) return -1;
2396 4 : tl_read_int32(r); /* id */
2397 4 : return 0;
2398 3 : case CRC_storyItemSkipped: {
2399 3 : if (r->len - r->pos < 4) return -1;
2400 3 : tl_read_uint32(r); /* flags */
2401 3 : if (r->len - r->pos < 12) return -1;
2402 3 : tl_read_int32(r); /* id */
2403 3 : tl_read_int32(r); /* date */
2404 3 : tl_read_int32(r); /* expire_date */
2405 3 : return 0;
2406 : }
2407 23 : case CRC_storyItem: {
2408 23 : if (r->len - r->pos < 4) return -1;
2409 22 : uint32_t flags = tl_read_uint32(r);
2410 22 : if (r->len - r->pos < 8) return -1;
2411 22 : tl_read_int32(r); /* id */
2412 22 : tl_read_int32(r); /* date */
2413 22 : if (flags & (1u << 18))
2414 0 : if (tl_skip_peer(r) != 0) return -1; /* from_id */
2415 22 : if (flags & (1u << 17))
2416 4 : if (skip_story_fwd_header(r) != 0) return -1; /* fwd_from */
2417 22 : if (r->len - r->pos < 4) return -1;
2418 22 : tl_read_int32(r); /* expire_date */
2419 22 : if (flags & (1u << 0))
2420 3 : if (tl_skip_string(r) != 0) return -1; /* caption */
2421 22 : if (flags & (1u << 1))
2422 3 : if (tl_skip_message_entities_vector(r) != 0) return -1;
2423 22 : if (tl_skip_message_media_ex(r, NULL) != 0) return -1;
2424 18 : if (flags & (1u << 14)) {
2425 9 : if (r->len - r->pos < 8) return -1;
2426 9 : uint32_t vc = tl_read_uint32(r);
2427 9 : if (vc != TL_vector) return -1;
2428 9 : uint32_t n = tl_read_uint32(r);
2429 24 : for (uint32_t i = 0; i < n; i++)
2430 17 : if (skip_media_area(r) != 0) return -1;
2431 : }
2432 16 : if (flags & (1u << 2)) {
2433 5 : if (r->len - r->pos < 8) return -1;
2434 5 : uint32_t vc = tl_read_uint32(r);
2435 5 : if (vc != TL_vector) return -1;
2436 5 : uint32_t n = tl_read_uint32(r);
2437 11 : for (uint32_t i = 0; i < n; i++)
2438 8 : if (skip_privacy_rule(r) != 0) return -1;
2439 : }
2440 14 : if (flags & (1u << 3))
2441 5 : if (skip_story_views(r) != 0) return -1;
2442 12 : if (flags & (1u << 15))
2443 0 : if (skip_reaction(r) != 0) return -1; /* sent_reaction */
2444 12 : return 0;
2445 : }
2446 2 : default:
2447 2 : logger_log(LOG_WARN, "skip_story_item: unknown 0x%08x", crc);
2448 2 : return -1;
2449 : }
2450 : }
2451 :
2452 : /* ---- MessageMedia skipper ----
2453 : * Covers all normal variants (photo, document, geo, contact, venue,
2454 : * poll, invoice, story-deleted/skipped, giveaway, game, paid, webpage).
2455 : *
2456 : * The dispatcher is re-entrant via messageMediaStory → storyItem →
2457 : * inner media:MessageMedia. An adversarial server could attempt
2458 : * unbounded recursion by chaining messageMediaStory inside itself.
2459 : * A depth counter caps the recursion at MEDIA_RECURSION_MAX; beyond
2460 : * that we bail with -1 and the caller stops iterating the enclosing
2461 : * Message. Realistic payloads never exceed depth 2.
2462 : */
2463 : #define MEDIA_RECURSION_MAX 4
2464 :
2465 : static int skip_message_media_body(TlReader *r, MediaInfo *out);
2466 :
2467 217 : int tl_skip_message_media_ex(TlReader *r, MediaInfo *out) {
2468 : static int recursion_depth = 0;
2469 217 : if (recursion_depth >= MEDIA_RECURSION_MAX) {
2470 1 : logger_log(LOG_WARN,
2471 : "tl_skip_message_media_ex: recursion depth limit (%d) reached",
2472 : MEDIA_RECURSION_MAX);
2473 1 : return -1;
2474 : }
2475 216 : recursion_depth++;
2476 216 : int rc = skip_message_media_body(r, out);
2477 216 : recursion_depth--;
2478 216 : return rc;
2479 : }
2480 :
2481 216 : static int skip_message_media_body(TlReader *r, MediaInfo *out) {
2482 216 : if (out) memset(out, 0, sizeof(*out));
2483 216 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2484 216 : uint32_t crc = tl_read_uint32(r);
2485 216 : switch (crc) {
2486 29 : case CRC_messageMediaEmpty:
2487 29 : if (out) out->kind = MEDIA_EMPTY;
2488 29 : return 0;
2489 3 : case CRC_messageMediaUnsupported:
2490 3 : if (out) out->kind = MEDIA_UNSUPPORTED;
2491 3 : return 0;
2492 :
2493 7 : case CRC_messageMediaGeo:
2494 7 : if (out) out->kind = MEDIA_GEO;
2495 7 : return skip_geo_point(r);
2496 :
2497 5 : case CRC_messageMediaContact:
2498 5 : if (out) out->kind = MEDIA_CONTACT;
2499 5 : if (tl_skip_string(r) != 0) return -1;
2500 5 : if (tl_skip_string(r) != 0) return -1;
2501 5 : if (tl_skip_string(r) != 0) return -1;
2502 5 : if (tl_skip_string(r) != 0) return -1;
2503 5 : if (r->len - r->pos < 8) return -1;
2504 5 : tl_read_int64(r);
2505 5 : return 0;
2506 :
2507 3 : case CRC_messageMediaVenue:
2508 3 : if (out) out->kind = MEDIA_VENUE;
2509 3 : if (skip_geo_point(r) != 0) return -1;
2510 3 : if (tl_skip_string(r) != 0) return -1;
2511 3 : if (tl_skip_string(r) != 0) return -1;
2512 3 : if (tl_skip_string(r) != 0) return -1;
2513 3 : if (tl_skip_string(r) != 0) return -1;
2514 3 : if (tl_skip_string(r) != 0) return -1;
2515 3 : return 0;
2516 :
2517 2 : case CRC_messageMediaGeoLive: {
2518 2 : if (out) out->kind = MEDIA_GEO_LIVE;
2519 2 : if (r->len - r->pos < 4) return -1;
2520 2 : uint32_t flags = tl_read_uint32(r);
2521 2 : if (skip_geo_point(r) != 0) return -1;
2522 2 : if (flags & 1u) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2523 2 : if (r->len - r->pos < 4) return -1;
2524 2 : tl_read_int32(r);
2525 2 : if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2526 2 : return 0;
2527 : }
2528 :
2529 3 : case CRC_messageMediaDice:
2530 3 : if (out) out->kind = MEDIA_DICE;
2531 3 : if (r->len - r->pos < 4) return -1;
2532 3 : tl_read_int32(r);
2533 3 : return tl_skip_string(r);
2534 :
2535 9 : case CRC_messageMediaPhoto: {
2536 9 : if (out) out->kind = MEDIA_PHOTO;
2537 9 : if (r->len - r->pos < 4) return -1;
2538 9 : uint32_t flags = tl_read_uint32(r);
2539 9 : if (flags & (1u << 0)) {
2540 9 : if (photo_full(r, out) != 0) return -1;
2541 : }
2542 9 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2543 9 : return 0;
2544 : }
2545 :
2546 52 : case CRC_messageMediaWebPage: {
2547 52 : if (out) out->kind = MEDIA_WEBPAGE;
2548 52 : if (r->len - r->pos < 4) return -1;
2549 52 : uint32_t flags = tl_read_uint32(r);
2550 : (void)flags; /* only boolean previews */
2551 52 : return skip_webpage(r);
2552 : }
2553 :
2554 10 : case CRC_messageMediaPoll: {
2555 10 : if (out) out->kind = MEDIA_POLL;
2556 10 : if (skip_poll(r) != 0) return -1;
2557 10 : return skip_poll_results(r);
2558 : }
2559 :
2560 11 : case CRC_messageMediaInvoice: {
2561 11 : if (out) out->kind = MEDIA_INVOICE;
2562 11 : if (r->len - r->pos < 4) return -1;
2563 11 : uint32_t flags = tl_read_uint32(r);
2564 11 : if (tl_skip_string(r) != 0) return -1; /* title */
2565 11 : if (tl_skip_string(r) != 0) return -1; /* description */
2566 11 : if (flags & (1u << 0)) {
2567 9 : if (skip_web_document(r) != 0) return -1;
2568 : }
2569 8 : if (flags & (1u << 2)) {
2570 0 : if (r->len - r->pos < 4) return -1;
2571 0 : tl_read_int32(r); /* receipt_msg_id */
2572 : }
2573 8 : if (tl_skip_string(r) != 0) return -1; /* currency */
2574 8 : if (r->len - r->pos < 8) return -1;
2575 8 : tl_read_int64(r); /* total_amount */
2576 8 : if (tl_skip_string(r) != 0) return -1; /* start_param */
2577 8 : if (flags & (1u << 4)) {
2578 1 : if (skip_message_extended_media(r) != 0) return -1;
2579 : }
2580 8 : return 0;
2581 : }
2582 :
2583 32 : case CRC_messageMediaStory: {
2584 32 : if (out) out->kind = MEDIA_STORY;
2585 32 : if (r->len - r->pos < 4) return -1;
2586 32 : uint32_t flags = tl_read_uint32(r);
2587 32 : if (tl_skip_peer(r) != 0) return -1; /* peer */
2588 32 : if (r->len - r->pos < 4) return -1;
2589 32 : tl_read_int32(r); /* id */
2590 32 : if (flags & (1u << 0)) {
2591 31 : if (skip_story_item(r) != 0) return -1;
2592 : }
2593 19 : return 0;
2594 : }
2595 :
2596 2 : case CRC_messageMediaGiveaway: {
2597 2 : if (out) out->kind = MEDIA_GIVEAWAY;
2598 2 : if (r->len - r->pos < 4) return -1;
2599 2 : uint32_t flags = tl_read_uint32(r);
2600 : /* channels:Vector<long> */
2601 2 : if (r->len - r->pos < 8) return -1;
2602 2 : uint32_t vc = tl_read_uint32(r);
2603 2 : if (vc != TL_vector) return -1;
2604 2 : uint32_t n_ch = tl_read_uint32(r);
2605 2 : if (r->len - r->pos < (size_t)n_ch * 8) return -1;
2606 5 : for (uint32_t i = 0; i < n_ch; i++) tl_read_int64(r);
2607 2 : if (flags & (1u << 1)) {
2608 1 : if (r->len - r->pos < 8) return -1;
2609 1 : uint32_t cvc = tl_read_uint32(r);
2610 1 : if (cvc != TL_vector) return -1;
2611 1 : uint32_t n_c = tl_read_uint32(r);
2612 3 : for (uint32_t i = 0; i < n_c; i++)
2613 2 : if (tl_skip_string(r) != 0) return -1;
2614 : }
2615 2 : if (flags & (1u << 3))
2616 1 : if (tl_skip_string(r) != 0) return -1;
2617 2 : if (r->len - r->pos < 4) return -1;
2618 2 : tl_read_int32(r); /* quantity */
2619 2 : if (flags & (1u << 4)) {
2620 1 : if (r->len - r->pos < 4) return -1;
2621 1 : tl_read_int32(r); /* months */
2622 : }
2623 2 : if (flags & (1u << 5)) {
2624 0 : if (r->len - r->pos < 8) return -1;
2625 0 : tl_read_int64(r); /* stars */
2626 : }
2627 2 : if (r->len - r->pos < 4) return -1;
2628 2 : tl_read_int32(r); /* until_date */
2629 2 : return 0;
2630 : }
2631 :
2632 9 : case CRC_messageMediaGame: {
2633 9 : if (out) out->kind = MEDIA_GAME;
2634 9 : return skip_game(r);
2635 : }
2636 :
2637 12 : case CRC_messageMediaPaidMedia: {
2638 12 : if (out) out->kind = MEDIA_PAID;
2639 12 : if (r->len - r->pos < 8) return -1;
2640 12 : tl_read_int64(r); /* stars_amount */
2641 12 : if (r->len - r->pos < 8) return -1;
2642 12 : uint32_t vc = tl_read_uint32(r);
2643 12 : if (vc != TL_vector) return -1;
2644 12 : uint32_t n = tl_read_uint32(r);
2645 22 : for (uint32_t i = 0; i < n; i++) {
2646 13 : if (skip_message_extended_media(r) != 0) return -1;
2647 : }
2648 9 : return 0;
2649 : }
2650 :
2651 23 : case CRC_messageMediaDocument: {
2652 23 : if (out) out->kind = MEDIA_DOCUMENT;
2653 23 : if (r->len - r->pos < 4) return -1;
2654 23 : uint32_t flags = tl_read_uint32(r);
2655 23 : if (flags & ((1u << 5) | (1u << 9) | (1u << 10) | (1u << 11))) {
2656 0 : logger_log(LOG_WARN,
2657 : "tl_skip_message_media: document with unsupported flags 0x%x",
2658 : flags);
2659 0 : return -1;
2660 : }
2661 23 : if (flags & (1u << 0)) {
2662 23 : if (document_inner(r, out) != 0) return -1;
2663 : }
2664 23 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2665 23 : return 0;
2666 : }
2667 :
2668 4 : default:
2669 4 : if (out) out->kind = MEDIA_OTHER;
2670 4 : logger_log(LOG_WARN, "tl_skip_message_media: unsupported 0x%08x", crc);
2671 4 : return -1;
2672 : }
2673 : }
2674 :
2675 28 : int tl_skip_message_media(TlReader *r) {
2676 28 : return tl_skip_message_media_ex(r, NULL);
2677 : }
2678 :
2679 : /* ---- Chat / User helpers ---- */
2680 :
2681 : /* Read a TL string into a fixed-size buffer, truncating if necessary.
2682 : * Always NUL-terminates `out` unless out_cap == 0. Advances the cursor.
2683 : * Returns 0 on success, -1 on reader error. */
2684 33 : static int read_string_into(TlReader *r, char *out, size_t out_cap) {
2685 33 : char *s = tl_read_string(r);
2686 33 : if (!s) {
2687 0 : if (out_cap > 0) out[0] = '\0';
2688 0 : return -1;
2689 : }
2690 33 : if (out_cap > 0) {
2691 33 : size_t n = strlen(s);
2692 33 : if (n >= out_cap) n = out_cap - 1;
2693 33 : memcpy(out, s, n);
2694 33 : out[n] = '\0';
2695 : }
2696 33 : free(s);
2697 33 : return 0;
2698 : }
2699 :
2700 : /* Append `src` to `dst` (NUL-terminated), respecting dst_cap. */
2701 20 : static void str_append(char *dst, size_t dst_cap, const char *src) {
2702 20 : if (dst_cap == 0) return;
2703 20 : size_t cur = strlen(dst);
2704 20 : if (cur >= dst_cap - 1) return;
2705 20 : size_t room = dst_cap - 1 - cur;
2706 20 : size_t n = strlen(src);
2707 20 : if (n > room) n = room;
2708 20 : memcpy(dst + cur, src, n);
2709 20 : dst[cur + n] = '\0';
2710 : }
2711 :
2712 : /* ---- ChatPhoto ----
2713 : * chatPhotoEmpty#37c1011c
2714 : * chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long
2715 : * stripped_thumb:flags.1?bytes dc_id:int
2716 : */
2717 21 : int tl_skip_chat_photo(TlReader *r) {
2718 21 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2719 21 : uint32_t crc = tl_read_uint32(r);
2720 21 : if (crc == CRC_chatPhotoEmpty) return 0;
2721 6 : if (crc != CRC_chatPhoto) {
2722 2 : logger_log(LOG_WARN, "tl_skip_chat_photo: unknown 0x%08x", crc);
2723 2 : return -1;
2724 : }
2725 4 : if (r->len - r->pos < 4) return -1;
2726 4 : uint32_t flags = tl_read_uint32(r);
2727 : /* photo_id:long */
2728 4 : if (r->len - r->pos < 8) return -1;
2729 4 : tl_read_int64(r);
2730 4 : if (flags & (1u << 1)) {
2731 2 : if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
2732 : }
2733 : /* dc_id:int */
2734 4 : if (r->len - r->pos < 4) return -1;
2735 4 : tl_read_int32(r);
2736 4 : return 0;
2737 : }
2738 :
2739 : /* ---- UserProfilePhoto ----
2740 : * userProfilePhotoEmpty#4f11bae1
2741 : * userProfilePhoto#82d1f706 flags:# has_video:flags.0?true
2742 : * personal:flags.2?true photo_id:long
2743 : * stripped_thumb:flags.1?bytes dc_id:int
2744 : */
2745 6 : int tl_skip_user_profile_photo(TlReader *r) {
2746 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2747 6 : uint32_t crc = tl_read_uint32(r);
2748 6 : if (crc == CRC_userProfilePhotoEmpty) return 0;
2749 4 : if (crc != CRC_userProfilePhoto) {
2750 2 : logger_log(LOG_WARN, "tl_skip_user_profile_photo: unknown 0x%08x", crc);
2751 2 : return -1;
2752 : }
2753 2 : if (r->len - r->pos < 4) return -1;
2754 2 : uint32_t flags = tl_read_uint32(r);
2755 : /* photo_id:long */
2756 2 : if (r->len - r->pos < 8) return -1;
2757 2 : tl_read_int64(r);
2758 2 : if (flags & (1u << 1)) {
2759 0 : if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
2760 : }
2761 : /* dc_id:int */
2762 2 : if (r->len - r->pos < 4) return -1;
2763 2 : tl_read_int32(r);
2764 2 : return 0;
2765 : }
2766 :
2767 : /* ---- UserStatus ---- */
2768 14 : int tl_skip_user_status(TlReader *r) {
2769 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2770 14 : uint32_t crc = tl_read_uint32(r);
2771 14 : switch (crc) {
2772 2 : case CRC_userStatusEmpty:
2773 2 : return 0;
2774 10 : case CRC_userStatusOnline:
2775 : case CRC_userStatusOffline:
2776 : case CRC_userStatusRecently:
2777 : case CRC_userStatusLastWeek:
2778 : case CRC_userStatusLastMonth:
2779 10 : if (r->len - r->pos < 4) return -1;
2780 10 : tl_read_int32(r);
2781 10 : return 0;
2782 2 : default:
2783 2 : logger_log(LOG_WARN, "tl_skip_user_status: unknown 0x%08x", crc);
2784 2 : return -1;
2785 : }
2786 : }
2787 :
2788 : /* ---- Vector<RestrictionReason> ----
2789 : * restrictionReason#d072acb4 platform:string reason:string text:string
2790 : */
2791 11 : int tl_skip_restriction_reason_vector(TlReader *r) {
2792 11 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2793 11 : uint32_t vec_crc = tl_read_uint32(r);
2794 11 : if (vec_crc != TL_vector) {
2795 0 : logger_log(LOG_WARN,
2796 : "tl_skip_restriction_reason_vector: expected vector 0x%08x",
2797 : vec_crc);
2798 0 : return -1;
2799 : }
2800 11 : uint32_t count = tl_read_uint32(r);
2801 22 : for (uint32_t i = 0; i < count; i++) {
2802 13 : if (r->len - r->pos < 4) return -1;
2803 13 : uint32_t crc = tl_read_uint32(r);
2804 13 : if (crc != CRC_restrictionReason) {
2805 2 : logger_log(LOG_WARN,
2806 : "tl_skip_restriction_reason_vector: bad entry 0x%08x",
2807 : crc);
2808 2 : return -1;
2809 : }
2810 11 : if (tl_skip_string(r) != 0) return -1; /* platform */
2811 11 : if (tl_skip_string(r) != 0) return -1; /* reason */
2812 11 : if (tl_skip_string(r) != 0) return -1; /* text */
2813 : }
2814 9 : return 0;
2815 : }
2816 :
2817 : /* ---- Vector<Username> ----
2818 : * username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string
2819 : */
2820 4 : int tl_skip_username_vector(TlReader *r) {
2821 4 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2822 4 : uint32_t vec_crc = tl_read_uint32(r);
2823 4 : if (vec_crc != TL_vector) {
2824 0 : logger_log(LOG_WARN,
2825 : "tl_skip_username_vector: expected vector 0x%08x", vec_crc);
2826 0 : return -1;
2827 : }
2828 4 : uint32_t count = tl_read_uint32(r);
2829 8 : for (uint32_t i = 0; i < count; i++) {
2830 6 : if (r->len - r->pos < 8) return -1;
2831 6 : uint32_t crc = tl_read_uint32(r);
2832 6 : if (crc != CRC_username) {
2833 2 : logger_log(LOG_WARN,
2834 : "tl_skip_username_vector: bad entry 0x%08x", crc);
2835 2 : return -1;
2836 : }
2837 4 : tl_read_uint32(r); /* flags — only 'true' bits, no data */
2838 4 : if (tl_skip_string(r) != 0) return -1;
2839 : }
2840 2 : return 0;
2841 : }
2842 :
2843 : /* ---- PeerColor ----
2844 : * peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long
2845 : */
2846 4 : int tl_skip_peer_color(TlReader *r) {
2847 4 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2848 4 : uint32_t crc = tl_read_uint32(r);
2849 4 : if (crc != CRC_peerColor) {
2850 2 : logger_log(LOG_WARN, "tl_skip_peer_color: unknown 0x%08x", crc);
2851 2 : return -1;
2852 : }
2853 2 : uint32_t flags = tl_read_uint32(r);
2854 2 : if (flags & (1u << 0)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2855 2 : if (flags & (1u << 1)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
2856 2 : return 0;
2857 : }
2858 :
2859 : /* ---- EmojiStatus ---- */
2860 8 : int tl_skip_emoji_status(TlReader *r) {
2861 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2862 8 : uint32_t crc = tl_read_uint32(r);
2863 8 : switch (crc) {
2864 2 : case CRC_emojiStatusEmpty:
2865 2 : return 0;
2866 2 : case CRC_emojiStatus:
2867 2 : if (r->len - r->pos < 8) return -1;
2868 2 : tl_read_int64(r);
2869 2 : return 0;
2870 2 : case CRC_emojiStatusUntil:
2871 2 : if (r->len - r->pos < 12) return -1;
2872 2 : tl_read_int64(r); tl_read_int32(r);
2873 2 : return 0;
2874 2 : default:
2875 2 : logger_log(LOG_WARN, "tl_skip_emoji_status: unknown 0x%08x", crc);
2876 2 : return -1;
2877 : }
2878 : }
2879 :
2880 : /* ChatAdminRights#5fb224d5 flags:# — single uint32. */
2881 6 : static int skip_chat_admin_rights(TlReader *r) {
2882 6 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2883 6 : uint32_t crc = tl_read_uint32(r);
2884 6 : if (crc != CRC_chatAdminRights) {
2885 2 : logger_log(LOG_WARN, "skip_chat_admin_rights: unknown 0x%08x", crc);
2886 2 : return -1;
2887 : }
2888 4 : tl_read_uint32(r); /* flags */
2889 4 : return 0;
2890 : }
2891 :
2892 : /* ChatBannedRights#9f120418 flags:# until_date:int. */
2893 8 : static int skip_chat_banned_rights(TlReader *r) {
2894 8 : if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
2895 8 : uint32_t crc = tl_read_uint32(r);
2896 8 : if (crc != CRC_chatBannedRights) {
2897 2 : logger_log(LOG_WARN, "skip_chat_banned_rights: unknown 0x%08x", crc);
2898 2 : return -1;
2899 : }
2900 6 : tl_read_uint32(r); /* flags */
2901 6 : tl_read_int32(r); /* until_date */
2902 6 : return 0;
2903 : }
2904 :
2905 : /* ---- Core Chat extractor ----
2906 : *
2907 : * Covers:
2908 : * chatEmpty#29562865 id:long
2909 : * chat#41cbf256 (see header comment)
2910 : * chatForbidden#6592a1a7 id:long title:string
2911 : * channel#0aadfc8f (layer 170+)
2912 : * channelForbidden#17d493d5
2913 : *
2914 : * If `out` is non-NULL, populates (id, title). chatEmpty leaves title empty.
2915 : */
2916 26 : static int extract_chat_inner(TlReader *r, ChatSummary *out) {
2917 26 : if (out) {
2918 13 : out->id = 0;
2919 13 : out->access_hash = 0;
2920 13 : out->have_access_hash = 0;
2921 13 : out->title[0] = '\0';
2922 : }
2923 :
2924 26 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2925 26 : uint32_t crc = tl_read_uint32(r);
2926 :
2927 26 : if (crc == TL_chatEmpty) {
2928 5 : if (r->len - r->pos < 8) return -1;
2929 5 : int64_t id = tl_read_int64(r);
2930 5 : if (out) out->id = id;
2931 5 : return 0;
2932 : }
2933 :
2934 21 : if (crc == TL_chatForbidden) {
2935 4 : if (r->len - r->pos < 8) return -1;
2936 4 : int64_t id = tl_read_int64(r);
2937 4 : if (out) out->id = id;
2938 4 : if (out) {
2939 3 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2940 0 : return -1;
2941 : } else {
2942 1 : if (tl_skip_string(r) != 0) return -1;
2943 : }
2944 4 : return 0;
2945 : }
2946 :
2947 17 : if (crc == TL_chat) {
2948 8 : if (r->len - r->pos < 4) return -1;
2949 8 : uint32_t flags = tl_read_uint32(r);
2950 : /* migrated_to:flags.6?InputChannel — too complex to dispatch here. */
2951 8 : if (flags & (1u << 6)) {
2952 0 : logger_log(LOG_WARN,
2953 : "extract_chat: chat with migrated_to not supported");
2954 0 : return -1;
2955 : }
2956 : /* id:long title:string photo:ChatPhoto */
2957 8 : if (r->len - r->pos < 8) return -1;
2958 8 : int64_t id = tl_read_int64(r);
2959 8 : if (out) out->id = id;
2960 8 : if (out) {
2961 3 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2962 0 : return -1;
2963 : } else {
2964 5 : if (tl_skip_string(r) != 0) return -1;
2965 : }
2966 8 : if (tl_skip_chat_photo(r) != 0) return -1;
2967 : /* participants_count:int date:int version:int */
2968 8 : if (r->len - r->pos < 12) return -1;
2969 8 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
2970 8 : if (flags & (1u << 14)) {
2971 4 : if (skip_chat_admin_rights(r) != 0) return -1;
2972 : }
2973 6 : if (flags & (1u << 18)) {
2974 4 : if (skip_chat_banned_rights(r) != 0) return -1;
2975 : }
2976 4 : return 0;
2977 : }
2978 :
2979 9 : if (crc == TL_channelForbidden) {
2980 2 : if (r->len - r->pos < 4) return -1;
2981 2 : uint32_t flags = tl_read_uint32(r);
2982 2 : if (r->len - r->pos < 16) return -1;
2983 2 : int64_t id = tl_read_int64(r);
2984 2 : if (out) out->id = id;
2985 2 : int64_t access_hash = tl_read_int64(r);
2986 2 : if (out) {
2987 1 : out->access_hash = access_hash;
2988 1 : out->have_access_hash = 1;
2989 : }
2990 2 : if (out) {
2991 1 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2992 0 : return -1;
2993 : } else {
2994 1 : if (tl_skip_string(r) != 0) return -1;
2995 : }
2996 2 : if (flags & (1u << 16)) {
2997 0 : if (r->len - r->pos < 4) return -1;
2998 0 : tl_read_int32(r); /* until_date */
2999 : }
3000 2 : return 0;
3001 : }
3002 :
3003 7 : if (crc == TL_channel) {
3004 5 : if (r->len - r->pos < 8) return -1;
3005 5 : uint32_t flags = tl_read_uint32(r);
3006 5 : uint32_t flags2 = tl_read_uint32(r);
3007 : /* Known flags2 bits: 0,4,7,8,9,10,11. Reject any others. */
3008 5 : const uint32_t flags2_known =
3009 : (1u << 0) | (1u << 4) | (1u << 7) | (1u << 8) |
3010 : (1u << 9) | (1u << 10) | (1u << 11);
3011 5 : if (flags2 & ~flags2_known) {
3012 0 : logger_log(LOG_WARN,
3013 : "extract_chat: channel with unknown flags2 bits 0x%x",
3014 : flags2);
3015 0 : return -1;
3016 : }
3017 5 : if (r->len - r->pos < 8) return -1;
3018 5 : int64_t id = tl_read_int64(r);
3019 5 : if (out) out->id = id;
3020 5 : if (flags & (1u << 13)) {
3021 2 : if (r->len - r->pos < 8) return -1;
3022 2 : int64_t access_hash = tl_read_int64(r);
3023 2 : if (out) {
3024 2 : out->access_hash = access_hash;
3025 2 : out->have_access_hash = 1;
3026 : }
3027 : }
3028 5 : if (out) {
3029 4 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
3030 0 : return -1;
3031 : } else {
3032 1 : if (tl_skip_string(r) != 0) return -1;
3033 : }
3034 5 : if (flags & (1u << 6)) {
3035 0 : if (tl_skip_string(r) != 0) return -1; /* username */
3036 : }
3037 5 : if (tl_skip_chat_photo(r) != 0) return -1;
3038 : /* date:int */
3039 5 : if (r->len - r->pos < 4) return -1;
3040 5 : tl_read_int32(r);
3041 5 : if (flags & (1u << 9)) {
3042 0 : if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3043 : }
3044 5 : if (flags & (1u << 14)) {
3045 2 : if (skip_chat_admin_rights(r) != 0) return -1;
3046 : }
3047 5 : if (flags & (1u << 15)) {
3048 2 : if (skip_chat_banned_rights(r) != 0) return -1;
3049 : }
3050 5 : if (flags & (1u << 18)) {
3051 2 : if (skip_chat_banned_rights(r) != 0) return -1;
3052 : }
3053 5 : if (flags & (1u << 17)) {
3054 0 : if (r->len - r->pos < 4) return -1;
3055 0 : tl_read_int32(r); /* participants_count */
3056 : }
3057 5 : if (flags2 & (1u << 0)) {
3058 0 : if (tl_skip_username_vector(r) != 0) return -1;
3059 : }
3060 5 : if (flags2 & (1u << 4)) {
3061 0 : if (r->len - r->pos < 4) return -1;
3062 0 : tl_read_int32(r); /* stories_max_id */
3063 : }
3064 5 : if (flags2 & (1u << 7)) {
3065 0 : if (tl_skip_peer_color(r) != 0) return -1;
3066 : }
3067 5 : if (flags2 & (1u << 8)) {
3068 0 : if (tl_skip_peer_color(r) != 0) return -1;
3069 : }
3070 5 : if (flags2 & (1u << 9)) {
3071 0 : if (tl_skip_emoji_status(r) != 0) return -1;
3072 : }
3073 5 : if (flags2 & (1u << 10)) {
3074 0 : if (r->len - r->pos < 4) return -1;
3075 0 : tl_read_int32(r); /* level */
3076 : }
3077 5 : if (flags2 & (1u << 11)) {
3078 0 : if (r->len - r->pos < 4) return -1;
3079 0 : tl_read_int32(r); /* subscription_until_date */
3080 : }
3081 5 : return 0;
3082 : }
3083 :
3084 2 : logger_log(LOG_WARN, "tl_skip_chat: unknown Chat variant 0x%08x", crc);
3085 2 : return -1;
3086 : }
3087 :
3088 9 : int tl_skip_chat(TlReader *r) {
3089 9 : return extract_chat_inner(r, NULL);
3090 : }
3091 :
3092 17 : int tl_extract_chat(TlReader *r, ChatSummary *out) {
3093 17 : if (!out) return extract_chat_inner(r, NULL);
3094 13 : return extract_chat_inner(r, out);
3095 : }
3096 :
3097 : /* ---- Core User extractor ----
3098 : *
3099 : * Covers:
3100 : * userEmpty#d3bc4b7a id:long
3101 : * user#3ff6ecb0 (layer 170+)
3102 : *
3103 : * If `out` is non-NULL, populates (id, name, username). `name` is
3104 : * `first_name` and `last_name` joined with a single space where both are
3105 : * present.
3106 : */
3107 20 : static int extract_user_inner(TlReader *r, UserSummary *out) {
3108 20 : if (out) {
3109 16 : out->id = 0;
3110 16 : out->access_hash = 0;
3111 16 : out->have_access_hash = 0;
3112 16 : out->name[0] = '\0';
3113 16 : out->username[0] = '\0';
3114 : }
3115 :
3116 20 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
3117 20 : uint32_t crc = tl_read_uint32(r);
3118 :
3119 20 : if (crc == TL_userEmpty) {
3120 3 : if (r->len - r->pos < 8) return -1;
3121 3 : int64_t id = tl_read_int64(r);
3122 3 : if (out) out->id = id;
3123 3 : return 0;
3124 : }
3125 :
3126 17 : if (crc != TL_user) {
3127 2 : logger_log(LOG_WARN, "tl_skip_user: unknown User variant 0x%08x", crc);
3128 2 : return -1;
3129 : }
3130 :
3131 15 : if (r->len - r->pos < 8) return -1;
3132 15 : uint32_t flags = tl_read_uint32(r);
3133 15 : uint32_t flags2 = tl_read_uint32(r);
3134 : /* Known flags2 bits go up to 13. Reject any bits above that. */
3135 15 : if (flags2 & ~((1u << 14) - 1u)) {
3136 0 : logger_log(LOG_WARN,
3137 : "tl_skip_user: unknown flags2 bits 0x%x", flags2);
3138 0 : return -1;
3139 : }
3140 15 : if (r->len - r->pos < 8) return -1;
3141 15 : int64_t id = tl_read_int64(r);
3142 15 : if (out) out->id = id;
3143 :
3144 15 : if (flags & (1u << 0)) {
3145 12 : if (r->len - r->pos < 8) return -1;
3146 12 : int64_t access_hash = tl_read_int64(r);
3147 12 : if (out) {
3148 12 : out->access_hash = access_hash;
3149 12 : out->have_access_hash = 1;
3150 : }
3151 : }
3152 : /* first_name */
3153 15 : if (flags & (1u << 1)) {
3154 13 : if (out) {
3155 12 : char first[96] = {0};
3156 12 : if (read_string_into(r, first, sizeof(first)) != 0) return -1;
3157 12 : str_append(out->name, sizeof(out->name), first);
3158 : } else {
3159 1 : if (tl_skip_string(r) != 0) return -1;
3160 : }
3161 : }
3162 : /* last_name */
3163 15 : if (flags & (1u << 2)) {
3164 5 : if (out) {
3165 4 : char last[96] = {0};
3166 4 : if (read_string_into(r, last, sizeof(last)) != 0) return -1;
3167 4 : if (last[0] != '\0') {
3168 4 : if (out->name[0] != '\0') {
3169 4 : str_append(out->name, sizeof(out->name), " ");
3170 : }
3171 4 : str_append(out->name, sizeof(out->name), last);
3172 : }
3173 : } else {
3174 1 : if (tl_skip_string(r) != 0) return -1;
3175 : }
3176 : }
3177 : /* username */
3178 15 : if (flags & (1u << 3)) {
3179 7 : if (out) {
3180 6 : if (read_string_into(r, out->username, sizeof(out->username)) != 0)
3181 0 : return -1;
3182 : } else {
3183 1 : if (tl_skip_string(r) != 0) return -1;
3184 : }
3185 : }
3186 : /* phone */
3187 15 : if (flags & (1u << 4)) {
3188 2 : if (tl_skip_string(r) != 0) return -1;
3189 : }
3190 : /* photo */
3191 15 : if (flags & (1u << 5)) {
3192 0 : if (tl_skip_user_profile_photo(r) != 0) return -1;
3193 : }
3194 : /* status */
3195 15 : if (flags & (1u << 6)) {
3196 0 : if (tl_skip_user_status(r) != 0) return -1;
3197 : }
3198 : /* bot_info_version */
3199 15 : if (flags & (1u << 14)) {
3200 0 : if (r->len - r->pos < 4) return -1;
3201 0 : tl_read_int32(r);
3202 : }
3203 : /* restriction_reason */
3204 15 : if (flags & (1u << 18)) {
3205 0 : if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3206 : }
3207 : /* bot_inline_placeholder */
3208 15 : if (flags & (1u << 19)) {
3209 0 : if (tl_skip_string(r) != 0) return -1;
3210 : }
3211 : /* lang_code */
3212 15 : if (flags & (1u << 22)) {
3213 0 : if (tl_skip_string(r) != 0) return -1;
3214 : }
3215 : /* emoji_status */
3216 15 : if (flags & (1u << 30)) {
3217 0 : if (tl_skip_emoji_status(r) != 0) return -1;
3218 : }
3219 : /* usernames */
3220 15 : if (flags2 & (1u << 0)) {
3221 0 : if (tl_skip_username_vector(r) != 0) return -1;
3222 : }
3223 : /* stories_max_id */
3224 15 : if (flags2 & (1u << 5)) {
3225 0 : if (r->len - r->pos < 4) return -1;
3226 0 : tl_read_int32(r);
3227 : }
3228 : /* color */
3229 15 : if (flags2 & (1u << 8)) {
3230 0 : if (tl_skip_peer_color(r) != 0) return -1;
3231 : }
3232 : /* profile_color */
3233 15 : if (flags2 & (1u << 9)) {
3234 0 : if (tl_skip_peer_color(r) != 0) return -1;
3235 : }
3236 : /* bot_active_users */
3237 15 : if (flags2 & (1u << 12)) {
3238 0 : if (r->len - r->pos < 4) return -1;
3239 0 : tl_read_int32(r);
3240 : }
3241 15 : return 0;
3242 : }
3243 :
3244 4 : int tl_skip_user(TlReader *r) {
3245 4 : return extract_user_inner(r, NULL);
3246 : }
3247 :
3248 16 : int tl_extract_user(TlReader *r, UserSummary *out) {
3249 16 : if (!out) return extract_user_inner(r, NULL);
3250 16 : return extract_user_inner(r, out);
3251 : }
3252 :
3253 : /* ---- tl_skip_message ----
3254 : * Mirrors the parser in domain/read/history.c but discards all output.
3255 : * Returns 0 if the cursor is safely past the whole Message, -1 if we
3256 : * had to bail (cursor possibly mid-object).
3257 : */
3258 29 : int tl_skip_message(TlReader *r) {
3259 29 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
3260 27 : uint32_t crc = tl_read_uint32(r);
3261 :
3262 27 : if (crc == TL_messageEmpty) {
3263 : /* flags:# id:int peer_id:flags.0?Peer */
3264 4 : if (r->len - r->pos < 8) return -1;
3265 4 : uint32_t flags = tl_read_uint32(r);
3266 4 : tl_read_int32(r); /* id */
3267 4 : if (flags & 1u) {
3268 0 : if (tl_skip_peer(r) != 0) return -1;
3269 : }
3270 4 : return 0;
3271 : }
3272 :
3273 23 : if (crc == TL_messageService) {
3274 : /* action-heavy; we do not implement skipping yet. */
3275 2 : return -1;
3276 : }
3277 :
3278 21 : if (crc != TL_message) {
3279 2 : logger_log(LOG_WARN, "tl_skip_message: unknown 0x%08x", crc);
3280 2 : return -1;
3281 : }
3282 :
3283 19 : if (r->len - r->pos < 12) return -1;
3284 17 : uint32_t flags = tl_read_uint32(r);
3285 17 : uint32_t flags2 = tl_read_uint32(r);
3286 17 : tl_read_int32(r); /* id */
3287 :
3288 17 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1; /* from_id */
3289 17 : if (tl_skip_peer(r) != 0) return -1; /* peer_id */
3290 17 : if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) return -1; /* saved_peer_id */
3291 17 : if (flags & (1u << 2)) if (tl_skip_message_fwd_header(r) != 0) return -1;
3292 17 : if (flags & (1u << 11)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3293 17 : if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3294 17 : if (flags & (1u << 3)) if (tl_skip_message_reply_header(r) != 0) return -1;
3295 :
3296 17 : if (r->len - r->pos < 4) return -1;
3297 17 : tl_read_int32(r); /* date */
3298 17 : if (tl_skip_string(r) != 0) return -1; /* message */
3299 :
3300 17 : if (flags & (1u << 9)) if (tl_skip_message_media(r) != 0) return -1;
3301 :
3302 15 : if (flags & (1u << 6)) if (tl_skip_reply_markup(r) != 0) return -1;
3303 13 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) return -1;
3304 13 : if (flags & (1u << 10)) { if (r->len - r->pos < 8) return -1; tl_read_int32(r); tl_read_int32(r); }
3305 13 : if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) return -1;
3306 13 : if (flags & (1u << 15)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3307 13 : if (flags & (1u << 16)) if (tl_skip_string(r) != 0) return -1;
3308 13 : if (flags & (1u << 17)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3309 13 : if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) return -1;
3310 13 : if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3311 13 : if (flags & (1u << 25)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3312 13 : if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3313 13 : if (flags2 & (1u << 2)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3314 13 : if (flags2 & (1u << 3)) if (tl_skip_factcheck(r) != 0) return -1;
3315 13 : return 0;
3316 : }
|