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 6 : int tl_skip_bool(TlReader *r) {
303 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
304 5 : tl_read_uint32(r);
305 5 : return 0;
306 : }
307 :
308 2001 : int tl_skip_string(TlReader *r) {
309 : /* tl_read_string already advances the cursor and handles padding. */
310 2001 : if (!tl_reader_ok(r)) return -1;
311 2000 : char *s = tl_read_string(r);
312 2000 : if (!s) return -1;
313 2000 : free(s);
314 2000 : return 0;
315 : }
316 :
317 1141 : int tl_skip_peer(TlReader *r) {
318 1141 : if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
319 1140 : uint32_t crc = tl_read_uint32(r);
320 1140 : (void)tl_read_int64(r);
321 1140 : switch (crc) {
322 1139 : case TL_peerUser:
323 : case TL_peerChat:
324 : case TL_peerChannel:
325 1139 : return 0;
326 1 : default:
327 1 : logger_log(LOG_WARN, "tl_skip_peer: unknown Peer 0x%08x", crc);
328 1 : return -1;
329 : }
330 : }
331 :
332 8 : int tl_skip_notification_sound(TlReader *r) {
333 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
334 8 : uint32_t crc = tl_read_uint32(r);
335 8 : switch (crc) {
336 5 : case CRC_notificationSoundDefault:
337 : case CRC_notificationSoundNone:
338 5 : return 0; /* no payload */
339 1 : case CRC_notificationSoundRingtone:
340 1 : if (r->len - r->pos < 8) return -1;
341 1 : tl_read_int64(r); /* id */
342 1 : return 0;
343 1 : case CRC_notificationSoundLocal:
344 1 : if (tl_skip_string(r) != 0) return -1; /* title */
345 1 : if (tl_skip_string(r) != 0) return -1; /* data */
346 1 : return 0;
347 1 : default:
348 1 : logger_log(LOG_WARN, "tl_skip_notification_sound: unknown 0x%08x", crc);
349 1 : 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 420 : int tl_skip_peer_notify_settings(TlReader *r) {
364 420 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
365 420 : uint32_t crc = tl_read_uint32(r);
366 420 : if (crc != CRC_peerNotifySettings) {
367 1 : logger_log(LOG_WARN,
368 : "tl_skip_peer_notify_settings: unexpected 0x%08x", crc);
369 1 : return -1;
370 : }
371 419 : uint32_t flags = tl_read_uint32(r);
372 :
373 419 : if (flags & (1u << 0)) if (tl_skip_bool(r) != 0) return -1;
374 419 : if (flags & (1u << 1)) if (tl_skip_bool(r) != 0) return -1;
375 419 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
376 419 : if (flags & (1u << 3)) if (tl_skip_notification_sound(r) != 0) return -1;
377 419 : if (flags & (1u << 4)) if (tl_skip_notification_sound(r) != 0) return -1;
378 419 : if (flags & (1u << 5)) if (tl_skip_notification_sound(r) != 0) return -1;
379 419 : if (flags & (1u << 6)) if (tl_skip_bool(r) != 0) return -1;
380 419 : if (flags & (1u << 7)) if (tl_skip_bool(r) != 0) return -1;
381 419 : if (flags & (1u << 8)) if (tl_skip_notification_sound(r) != 0) return -1;
382 419 : if (flags & (1u << 9)) if (tl_skip_notification_sound(r) != 0) return -1;
383 419 : if (flags & (1u << 10)) if (tl_skip_notification_sound(r) != 0) return -1;
384 419 : 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 4 : int tl_skip_draft_message(TlReader *r) {
394 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
395 4 : uint32_t crc = tl_read_uint32(r);
396 4 : if (crc == CRC_draftMessageEmpty) {
397 2 : if (r->len - r->pos < 4) return -1;
398 2 : uint32_t flags = tl_read_uint32(r);
399 2 : if (flags & 1u) {
400 1 : if (r->len - r->pos < 4) return -1;
401 1 : tl_read_int32(r); /* date */
402 : }
403 2 : return 0;
404 : }
405 2 : if (crc == CRC_draftMessage) {
406 1 : logger_log(LOG_WARN,
407 : "tl_skip_draft_message: non-empty draft not parseable yet");
408 1 : return -1;
409 : }
410 1 : logger_log(LOG_WARN, "tl_skip_draft_message: unknown 0x%08x", crc);
411 1 : return -1;
412 : }
413 :
414 10 : int tl_skip_message_entity(TlReader *r) {
415 10 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
416 10 : uint32_t crc = tl_read_uint32(r);
417 :
418 : /* All entities start with offset:int length:int (8 bytes). */
419 10 : switch (crc) {
420 5 : 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 5 : if (r->len - r->pos < 8) return -1;
436 5 : tl_read_int32(r); tl_read_int32(r);
437 5 : 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 1 : case CRC_messageEntityTextUrl:
445 1 : if (r->len - r->pos < 8) return -1;
446 1 : tl_read_int32(r); tl_read_int32(r);
447 1 : return tl_skip_string(r); /* url */
448 :
449 1 : case CRC_messageEntityMentionName:
450 1 : if (r->len - r->pos < 16) return -1;
451 1 : tl_read_int32(r); tl_read_int32(r);
452 1 : tl_read_int64(r); /* user_id */
453 1 : return 0;
454 :
455 1 : case CRC_messageEntityCustomEmoji:
456 1 : if (r->len - r->pos < 16) return -1;
457 1 : tl_read_int32(r); tl_read_int32(r);
458 1 : tl_read_int64(r); /* document_id */
459 1 : return 0;
460 :
461 1 : case CRC_messageEntityBlockquote:
462 : /* flags:# offset:int length:int — no string payload */
463 1 : if (r->len - r->pos < 12) return -1;
464 1 : tl_read_uint32(r); /* flags */
465 1 : tl_read_int32(r); tl_read_int32(r);
466 1 : return 0;
467 :
468 1 : default:
469 1 : logger_log(LOG_WARN, "tl_skip_message_entity: unknown 0x%08x", crc);
470 1 : return -1;
471 : }
472 : }
473 :
474 18 : int tl_skip_message_entities_vector(TlReader *r) {
475 18 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
476 18 : uint32_t vec_crc = tl_read_uint32(r);
477 18 : if (vec_crc != TL_vector) {
478 0 : logger_log(LOG_WARN,
479 : "tl_skip_message_entities_vector: expected vector 0x%08x",
480 : vec_crc);
481 0 : return -1;
482 : }
483 18 : uint32_t count = tl_read_uint32(r);
484 27 : for (uint32_t i = 0; i < count; i++) {
485 10 : if (tl_skip_message_entity(r) != 0) return -1;
486 : }
487 17 : return 0;
488 : }
489 :
490 : /* ---- ReplyMarkup skipper ---- */
491 :
492 : /* Skip a KeyboardButton. Returns -1 on unknown variant. */
493 5 : static int skip_keyboard_button(TlReader *r) {
494 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
495 5 : uint32_t crc = tl_read_uint32(r);
496 5 : switch (crc) {
497 1 : case CRC_keyboardButton:
498 : case CRC_keyboardButtonRequestPhone:
499 : case CRC_keyboardButtonRequestGeoLoc:
500 : case CRC_keyboardButtonGame:
501 : case CRC_keyboardButtonBuy:
502 1 : return tl_skip_string(r); /* text */
503 1 : case CRC_keyboardButtonUrl: {
504 1 : if (tl_skip_string(r) != 0) return -1; /* text */
505 1 : return tl_skip_string(r); /* url */
506 : }
507 3 : case CRC_keyboardButtonCallback: {
508 3 : if (r->len - r->pos < 4) return -1;
509 3 : tl_read_uint32(r); /* flags */
510 3 : if (tl_skip_string(r) != 0) return -1; /* text */
511 3 : 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 0 : default:
559 0 : logger_log(LOG_WARN, "skip_keyboard_button: unknown 0x%08x", crc);
560 0 : return -1;
561 : }
562 : }
563 :
564 : /* Skip a KeyboardButtonRow = crc + Vector<KeyboardButton>. */
565 4 : static int skip_keyboard_button_row(TlReader *r) {
566 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
567 4 : uint32_t crc = tl_read_uint32(r);
568 4 : 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 4 : if (r->len - r->pos < 8) return -1;
575 4 : uint32_t vec_crc = tl_read_uint32(r);
576 4 : if (vec_crc != TL_vector) return -1;
577 4 : uint32_t n = tl_read_uint32(r);
578 9 : for (uint32_t i = 0; i < n; i++) {
579 5 : if (skip_keyboard_button(r) != 0) return -1;
580 : }
581 4 : return 0;
582 : }
583 :
584 : /* Skip a Vector<KeyboardButtonRow>. */
585 5 : static int skip_button_row_vector(TlReader *r) {
586 5 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
587 5 : uint32_t vec_crc = tl_read_uint32(r);
588 5 : if (vec_crc != TL_vector) return -1;
589 5 : uint32_t n = tl_read_uint32(r);
590 9 : for (uint32_t i = 0; i < n; i++) {
591 4 : if (skip_keyboard_button_row(r) != 0) return -1;
592 : }
593 5 : return 0;
594 : }
595 :
596 9 : int tl_skip_reply_markup(TlReader *r) {
597 9 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
598 9 : uint32_t crc = tl_read_uint32(r);
599 9 : switch (crc) {
600 2 : case CRC_replyKeyboardHide:
601 : case CRC_replyKeyboardForceReply: {
602 : /* Body: flags:# (+ optional placeholder:flags.3?string for
603 : * forceReply). */
604 2 : if (r->len - r->pos < 4) return -1;
605 2 : uint32_t flags = tl_read_uint32(r);
606 2 : if (crc == CRC_replyKeyboardForceReply && (flags & (1u << 3))) {
607 1 : if (tl_skip_string(r) != 0) return -1; /* placeholder */
608 : }
609 2 : 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 5 : case CRC_replyInlineMarkup:
621 5 : return skip_button_row_vector(r);
622 2 : default:
623 2 : logger_log(LOG_WARN, "tl_skip_reply_markup: unknown 0x%08x", crc);
624 2 : return -1;
625 : }
626 : }
627 :
628 : /* ---- MessageReactions skipper ---- */
629 :
630 : /* Skip a Reaction variant. */
631 8 : static int skip_reaction(TlReader *r) {
632 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
633 8 : uint32_t crc = tl_read_uint32(r);
634 8 : switch (crc) {
635 2 : case CRC_reactionEmpty:
636 : case CRC_reactionPaid:
637 2 : return 0;
638 4 : case CRC_reactionEmoji:
639 4 : return tl_skip_string(r);
640 1 : case CRC_reactionCustomEmoji:
641 1 : if (r->len - r->pos < 8) return -1;
642 1 : tl_read_int64(r); /* document_id */
643 1 : return 0;
644 1 : default:
645 1 : logger_log(LOG_WARN, "skip_reaction: unknown 0x%08x", crc);
646 1 : return -1;
647 : }
648 : }
649 :
650 : /* Skip a ReactionCount#a3d1cb80: flags + (chosen_order) + Reaction + count. */
651 7 : static int skip_reaction_count(TlReader *r) {
652 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
653 7 : uint32_t crc = tl_read_uint32(r);
654 7 : 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 7 : if (r->len - r->pos < 4) return -1;
660 7 : uint32_t flags = tl_read_uint32(r);
661 7 : if (flags & (1u << 0)) {
662 1 : if (r->len - r->pos < 4) return -1;
663 1 : tl_read_int32(r); /* chosen_order */
664 : }
665 7 : if (skip_reaction(r) != 0) return -1;
666 6 : if (r->len - r->pos < 4) return -1;
667 6 : tl_read_int32(r); /* count */
668 6 : return 0;
669 : }
670 :
671 6 : int tl_skip_message_reactions(TlReader *r) {
672 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
673 6 : uint32_t crc = tl_read_uint32(r);
674 6 : if (crc != CRC_messageReactions) {
675 0 : logger_log(LOG_WARN, "tl_skip_message_reactions: unknown 0x%08x", crc);
676 0 : return -1;
677 : }
678 6 : if (r->len - r->pos < 4) return -1;
679 6 : uint32_t flags = tl_read_uint32(r);
680 : /* results:Vector<ReactionCount> — required. */
681 6 : if (r->len - r->pos < 8) return -1;
682 6 : uint32_t vec_crc = tl_read_uint32(r);
683 6 : if (vec_crc != TL_vector) return -1;
684 6 : uint32_t n = tl_read_uint32(r);
685 11 : for (uint32_t i = 0; i < n; i++) {
686 6 : 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 5 : if (flags & ((1u << 1) | (1u << 2))) {
691 1 : logger_log(LOG_WARN,
692 : "tl_skip_message_reactions: recent/top reactors present "
693 : "(flags=0x%08x) — not supported", flags);
694 1 : return -1;
695 : }
696 4 : 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 6 : int tl_skip_message_replies(TlReader *r) {
707 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
708 6 : uint32_t crc = tl_read_uint32(r);
709 6 : if (crc != CRC_messageReplies) {
710 1 : logger_log(LOG_WARN, "tl_skip_message_replies: unknown 0x%08x", crc);
711 1 : return -1;
712 : }
713 5 : if (r->len - r->pos < 12) return -1;
714 5 : uint32_t flags = tl_read_uint32(r);
715 5 : tl_read_int32(r); /* replies */
716 5 : tl_read_int32(r); /* replies_pts */
717 5 : if (flags & (1u << 1)) {
718 1 : if (r->len - r->pos < 8) return -1;
719 1 : uint32_t vec_crc = tl_read_uint32(r);
720 1 : if (vec_crc != TL_vector) return -1;
721 1 : uint32_t n = tl_read_uint32(r);
722 2 : for (uint32_t i = 0; i < n; i++) {
723 1 : if (tl_skip_peer(r) != 0) return -1;
724 : }
725 : }
726 5 : if (flags & (1u << 0)) {
727 1 : if (r->len - r->pos < 8) return -1;
728 1 : tl_read_int64(r); /* channel_id */
729 : }
730 5 : if (flags & (1u << 2)) {
731 1 : if (r->len - r->pos < 4) return -1;
732 1 : tl_read_int32(r); /* max_id */
733 : }
734 5 : if (flags & (1u << 3)) {
735 1 : if (r->len - r->pos < 4) return -1;
736 1 : tl_read_int32(r); /* read_max_id */
737 : }
738 5 : 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 6 : int tl_skip_factcheck(TlReader *r) {
751 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
752 6 : uint32_t crc = tl_read_uint32(r);
753 6 : if (crc != CRC_factCheck) {
754 1 : logger_log(LOG_WARN, "tl_skip_factcheck: unknown 0x%08x", crc);
755 1 : return -1;
756 : }
757 5 : if (r->len - r->pos < 4) return -1;
758 5 : uint32_t flags = tl_read_uint32(r);
759 5 : if (flags & (1u << 1)) {
760 4 : if (tl_skip_string(r) != 0) return -1; /* country */
761 : /* text:TextWithEntities */
762 4 : if (r->len - r->pos < 4) return -1;
763 4 : uint32_t twe_crc = tl_read_uint32(r);
764 4 : 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 4 : if (tl_skip_string(r) != 0) return -1; /* text */
771 4 : if (tl_skip_message_entities_vector(r) != 0) return -1;
772 : }
773 5 : if (r->len - r->pos < 8) return -1;
774 5 : tl_read_int64(r); /* hash */
775 5 : 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 5 : int tl_skip_message_fwd_header(TlReader *r) {
794 5 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
795 5 : uint32_t crc = tl_read_uint32(r);
796 5 : if (crc != CRC_messageFwdHeader) {
797 1 : logger_log(LOG_WARN,
798 : "tl_skip_message_fwd_header: unexpected 0x%08x", crc);
799 1 : return -1;
800 : }
801 4 : uint32_t flags = tl_read_uint32(r);
802 :
803 4 : if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
804 4 : if (flags & (1u << 5)) if (tl_skip_string(r) != 0) return -1;
805 4 : if (r->len - r->pos < 4) return -1;
806 4 : tl_read_int32(r); /* date */
807 4 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
808 4 : if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
809 4 : 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 4 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1;
815 4 : if (flags & (1u << 9)) if (tl_skip_string(r) != 0) return -1;
816 4 : if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
817 4 : if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
818 4 : 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 7 : int tl_skip_message_reply_header(TlReader *r) {
835 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
836 7 : uint32_t crc = tl_read_uint32(r);
837 7 : if (crc == CRC_messageReplyStoryHeader) {
838 : /* peer_id:Peer story_id:int */
839 1 : if (tl_skip_peer(r) != 0) return -1;
840 1 : if (r->len - r->pos < 4) return -1;
841 1 : tl_read_int32(r);
842 1 : return 0;
843 : }
844 6 : if (crc != CRC_messageReplyHeader) {
845 1 : logger_log(LOG_WARN,
846 : "tl_skip_message_reply_header: unexpected 0x%08x", crc);
847 1 : return -1;
848 : }
849 5 : if (r->len - r->pos < 4) return -1;
850 5 : uint32_t flags = tl_read_uint32(r);
851 :
852 5 : if (flags & (1u << 4)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
853 5 : if (flags & (1u << 0)) if (tl_skip_peer(r) != 0) return -1;
854 5 : if (flags & (1u << 5)) if (tl_skip_message_fwd_header(r) != 0) return -1;
855 5 : if (flags & (1u << 8)) {
856 0 : logger_log(LOG_WARN,
857 : "tl_skip_message_reply_header: reply_media not implemented");
858 0 : return -1;
859 : }
860 5 : if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
861 5 : if (flags & (1u << 6)) if (tl_skip_string(r) != 0) return -1;
862 5 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) return -1;
863 5 : if (flags & (1u << 10)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
864 5 : 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 6 : int tl_skip_photo_size(TlReader *r) {
877 6 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
878 6 : uint32_t crc = tl_read_uint32(r);
879 6 : switch (crc) {
880 2 : case CRC_photoSizeEmpty:
881 2 : return tl_skip_string(r);
882 1 : case CRC_photoSize:
883 1 : if (tl_skip_string(r) != 0) return -1;
884 1 : if (r->len - r->pos < 12) return -1;
885 1 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
886 1 : 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 2 : case CRC_photoStrippedSize:
893 : case CRC_photoPathSize:
894 2 : if (tl_skip_string(r) != 0) return -1;
895 2 : 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 1 : default:
910 1 : logger_log(LOG_WARN, "tl_skip_photo_size: unknown 0x%08x", crc);
911 1 : return -1;
912 : }
913 : }
914 :
915 1 : int tl_skip_photo_size_vector(TlReader *r) {
916 1 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
917 1 : uint32_t vec_crc = tl_read_uint32(r);
918 1 : if (vec_crc != TL_vector) return -1;
919 1 : uint32_t count = tl_read_uint32(r);
920 2 : for (uint32_t i = 0; i < count; i++) {
921 1 : if (tl_skip_photo_size(r) != 0) return -1;
922 : }
923 1 : 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 3 : static int walk_photo_size_vector(TlReader *r,
945 : char *best_type, size_t best_type_cap) {
946 3 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
947 3 : uint32_t vec_crc = tl_read_uint32(r);
948 3 : if (vec_crc != TL_vector) return -1;
949 3 : uint32_t count = tl_read_uint32(r);
950 :
951 3 : long best_score = -1;
952 3 : if (best_type && best_type_cap) best_type[0] = '\0';
953 :
954 8 : for (uint32_t i = 0; i < count; i++) {
955 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
956 5 : uint32_t crc = tl_read_uint32(r);
957 :
958 : /* Capture type:string into a small buffer we may keep as best. */
959 5 : char type_buf[8] = {0};
960 5 : size_t type_n = 0;
961 : {
962 5 : size_t before = r->pos;
963 5 : char *s = tl_read_string(r);
964 5 : if (!s) return -1;
965 5 : type_n = strlen(s);
966 5 : if (type_n >= sizeof(type_buf)) type_n = sizeof(type_buf) - 1;
967 5 : memcpy(type_buf, s, type_n);
968 5 : type_buf[type_n] = '\0';
969 5 : free(s);
970 : (void)before;
971 : }
972 :
973 5 : long score = 0;
974 5 : switch (crc) {
975 0 : case CRC_photoSizeEmpty:
976 0 : score = -1; /* empty — never best */
977 0 : break;
978 3 : case CRC_photoSize: {
979 3 : if (r->len - r->pos < 12) return -1;
980 3 : int32_t w = tl_read_int32(r);
981 3 : int32_t h = tl_read_int32(r);
982 3 : int32_t sz = tl_read_int32(r); (void)sz;
983 3 : score = (long)w * (long)h;
984 3 : break;
985 : }
986 1 : case CRC_photoCachedSize: {
987 1 : if (r->len - r->pos < 8) return -1;
988 1 : int32_t w = tl_read_int32(r);
989 1 : int32_t h = tl_read_int32(r);
990 1 : if (tl_skip_string(r) != 0) return -1; /* bytes */
991 1 : score = (long)w * (long)h;
992 1 : 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 1 : case CRC_photoSizeProgressive: {
1000 1 : if (r->len - r->pos < 8) return -1;
1001 1 : int32_t w = tl_read_int32(r);
1002 1 : int32_t h = tl_read_int32(r);
1003 1 : if (r->len - r->pos < 8) return -1;
1004 1 : uint32_t vc = tl_read_uint32(r);
1005 1 : if (vc != TL_vector) return -1;
1006 1 : uint32_t nvals = tl_read_uint32(r);
1007 1 : if (r->len - r->pos < nvals * 4ULL) return -1;
1008 3 : for (uint32_t j = 0; j < nvals; j++) tl_read_int32(r);
1009 1 : score = (long)w * (long)h;
1010 1 : 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 5 : if (score > best_score && best_type && best_type_cap) {
1018 2 : best_score = score;
1019 2 : size_t n = type_n < best_type_cap - 1 ? type_n : best_type_cap - 1;
1020 2 : memcpy(best_type, type_buf, n);
1021 2 : best_type[n] = '\0';
1022 : }
1023 : }
1024 3 : return 0;
1025 : }
1026 :
1027 7 : static int photo_full(TlReader *r, MediaInfo *out) {
1028 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1029 7 : uint32_t crc = tl_read_uint32(r);
1030 7 : if (crc == CRC_photoEmpty) {
1031 4 : if (r->len - r->pos < 8) return -1;
1032 4 : int64_t id = tl_read_int64(r);
1033 4 : if (out) out->photo_id = id;
1034 4 : return 0;
1035 : }
1036 3 : if (crc != CRC_photo) {
1037 0 : logger_log(LOG_WARN, "tl_skip_photo: unknown 0x%08x", crc);
1038 0 : return -1;
1039 : }
1040 3 : if (r->len - r->pos < 4) return -1;
1041 3 : uint32_t flags = tl_read_uint32(r);
1042 3 : if (r->len - r->pos < 16) return -1;
1043 3 : int64_t id = tl_read_int64(r);
1044 3 : int64_t access = tl_read_int64(r);
1045 3 : 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 3 : size_t fr_len = 0;
1050 3 : uint8_t *fr = tl_read_bytes(r, &fr_len);
1051 3 : if (!fr && fr_len != 0) return -1;
1052 3 : if (out) {
1053 2 : size_t n = fr_len;
1054 2 : if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
1055 2 : if (fr) memcpy(out->file_reference, fr, n);
1056 2 : out->file_reference_len = n;
1057 : }
1058 3 : free(fr);
1059 :
1060 3 : if (r->len - r->pos < 4) return -1;
1061 3 : tl_read_int32(r); /* date */
1062 :
1063 3 : 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 3 : if (flags & (1u << 1)) {
1069 0 : logger_log(LOG_WARN, "tl_skip_photo: video_sizes not implemented");
1070 0 : return -1;
1071 : }
1072 3 : if (r->len - r->pos < 4) return -1;
1073 3 : int32_t dc = tl_read_int32(r);
1074 3 : if (out) out->dc_id = dc;
1075 3 : return 0;
1076 : }
1077 :
1078 5 : int tl_skip_photo(TlReader *r) {
1079 5 : 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 1 : static int skip_input_sticker_set(TlReader *r) {
1123 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1124 1 : uint32_t crc = tl_read_uint32(r);
1125 1 : switch (crc) {
1126 1 : 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 1 : return 0;
1135 0 : case CRC_inputStickerSetID:
1136 0 : if (r->len - r->pos < 16) return -1;
1137 0 : tl_read_int64(r); /* id */
1138 0 : tl_read_int64(r); /* access_hash */
1139 0 : return 0;
1140 0 : case CRC_inputStickerSetShortName:
1141 0 : 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 0 : static int skip_mask_coords(TlReader *r) {
1153 0 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1154 0 : uint32_t crc = tl_read_uint32(r);
1155 0 : if (crc != CRC_maskCoords) {
1156 0 : logger_log(LOG_WARN, "skip_mask_coords: unknown 0x%08x", crc);
1157 0 : return -1;
1158 : }
1159 0 : if (r->len - r->pos < 4 + 3 * 8) return -1;
1160 0 : tl_read_int32(r); /* n */
1161 0 : tl_read_double(r); tl_read_double(r); tl_read_double(r); /* x, y, zoom */
1162 0 : return 0;
1163 : }
1164 :
1165 : /* Skip a single VideoSize variant (document.video_thumbs element). */
1166 0 : static int skip_video_size(TlReader *r) {
1167 0 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1168 0 : uint32_t crc = tl_read_uint32(r);
1169 0 : switch (crc) {
1170 0 : case CRC_videoSize: {
1171 : /* flags:# type:string w:int h:int size:int video_start_ts:flags.0?double */
1172 0 : if (r->len - r->pos < 4) return -1;
1173 0 : uint32_t flags = tl_read_uint32(r);
1174 0 : if (tl_skip_string(r) != 0) return -1;
1175 0 : if (r->len - r->pos < 12) return -1;
1176 0 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
1177 0 : if (flags & (1u << 0)) {
1178 0 : if (r->len - r->pos < 8) return -1;
1179 0 : tl_read_double(r);
1180 : }
1181 0 : 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 0 : static int skip_video_size_vector(TlReader *r) {
1215 0 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1216 0 : uint32_t vec = tl_read_uint32(r);
1217 0 : if (vec != TL_vector) return -1;
1218 0 : uint32_t n = tl_read_uint32(r);
1219 0 : for (uint32_t i = 0; i < n; i++) {
1220 0 : if (skip_video_size(r) != 0) return -1;
1221 : }
1222 0 : return 0;
1223 : }
1224 :
1225 : /* Skip a single DocumentAttribute, optionally extracting the filename. */
1226 14 : static int skip_document_attribute(TlReader *r,
1227 : char *filename_out, size_t fn_cap) {
1228 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1229 14 : uint32_t crc = tl_read_uint32(r);
1230 14 : switch (crc) {
1231 1 : case CRC_documentAttributeImageSize:
1232 1 : if (r->len - r->pos < 8) return -1;
1233 1 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1234 1 : return 0;
1235 4 : case CRC_documentAttributeAnimated:
1236 : case CRC_documentAttributeHasStickers:
1237 4 : return 0;
1238 3 : case CRC_documentAttributeFilename: {
1239 3 : size_t before = r->pos;
1240 3 : char *s = tl_read_string(r);
1241 3 : if (!s) { r->pos = before; return -1; }
1242 3 : if (filename_out && fn_cap) {
1243 2 : size_t n = strlen(s);
1244 2 : if (n >= fn_cap) n = fn_cap - 1;
1245 2 : memcpy(filename_out, s, n);
1246 2 : filename_out[n] = '\0';
1247 : }
1248 3 : free(s);
1249 3 : return 0;
1250 : }
1251 2 : case CRC_documentAttributeVideo: {
1252 2 : if (r->len - r->pos < 4) return -1;
1253 2 : uint32_t flags = tl_read_uint32(r);
1254 2 : if (r->len - r->pos < 8 + 4 + 4) return -1;
1255 2 : tl_read_double(r); /* duration */
1256 2 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1257 2 : 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 2 : 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 2 : if (flags & (1u << 5))
1266 0 : if (tl_skip_string(r) != 0) return -1; /* video_codec */
1267 2 : return 0;
1268 : }
1269 3 : case CRC_documentAttributeAudio: {
1270 3 : if (r->len - r->pos < 8) return -1;
1271 3 : uint32_t flags = tl_read_uint32(r);
1272 3 : tl_read_int32(r); /* duration */
1273 3 : if (flags & (1u << 0))
1274 0 : if (tl_skip_string(r) != 0) return -1; /* title */
1275 3 : if (flags & (1u << 1))
1276 0 : if (tl_skip_string(r) != 0) return -1; /* performer */
1277 3 : if (flags & (1u << 2))
1278 0 : if (tl_skip_string(r) != 0) return -1; /* waveform:bytes */
1279 3 : return 0;
1280 : }
1281 1 : case CRC_documentAttributeSticker: {
1282 : /* flags:# mask:flags.1?true alt:string stickerset:InputStickerSet
1283 : * mask_coords:flags.0?MaskCoords */
1284 1 : if (r->len - r->pos < 4) return -1;
1285 1 : uint32_t flags = tl_read_uint32(r);
1286 1 : if (tl_skip_string(r) != 0) return -1; /* alt */
1287 1 : if (skip_input_sticker_set(r) != 0) return -1;
1288 1 : if (flags & (1u << 0)) {
1289 0 : if (skip_mask_coords(r) != 0) return -1;
1290 : }
1291 1 : return 0;
1292 : }
1293 0 : case CRC_documentAttributeCustomEmoji: {
1294 : /* flags:# free:flags.0?true text_color:flags.1?true alt:string
1295 : * stickerset:InputStickerSet */
1296 0 : if (r->len - r->pos < 4) return -1;
1297 0 : (void)tl_read_uint32(r); /* flags */
1298 0 : if (tl_skip_string(r) != 0) return -1; /* alt */
1299 0 : 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 11 : static int document_inner(TlReader *r, MediaInfo *out) {
1311 11 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1312 11 : uint32_t crc = tl_read_uint32(r);
1313 11 : if (crc == CRC_documentEmpty) {
1314 2 : if (r->len - r->pos < 8) return -1;
1315 2 : int64_t id = tl_read_int64(r);
1316 2 : if (out) out->document_id = id;
1317 2 : return 0;
1318 : }
1319 9 : if (crc != CRC_document) {
1320 0 : logger_log(LOG_WARN, "tl_skip_document: unknown 0x%08x", crc);
1321 0 : return -1;
1322 : }
1323 9 : if (r->len - r->pos < 4) return -1;
1324 9 : uint32_t flags = tl_read_uint32(r);
1325 9 : if (r->len - r->pos < 16) return -1;
1326 9 : int64_t id = tl_read_int64(r);
1327 9 : int64_t access = tl_read_int64(r);
1328 9 : if (out) { out->document_id = id; out->access_hash = access; }
1329 :
1330 9 : size_t fr_len = 0;
1331 9 : uint8_t *fr = tl_read_bytes(r, &fr_len);
1332 9 : if (!fr && fr_len != 0) return -1;
1333 9 : if (out) {
1334 8 : size_t n = fr_len;
1335 8 : if (n > MEDIA_FILE_REF_MAX) n = MEDIA_FILE_REF_MAX;
1336 8 : if (fr) memcpy(out->file_reference, fr, n);
1337 8 : out->file_reference_len = n;
1338 : }
1339 9 : free(fr);
1340 :
1341 9 : if (r->len - r->pos < 4) return -1;
1342 9 : tl_read_int32(r); /* date */
1343 :
1344 9 : size_t mime_before = r->pos;
1345 9 : char *mime = tl_read_string(r);
1346 9 : if (!mime) { r->pos = mime_before; return -1; }
1347 9 : if (out) {
1348 8 : size_t n = strlen(mime);
1349 8 : if (n >= sizeof(out->document_mime)) n = sizeof(out->document_mime) - 1;
1350 8 : memcpy(out->document_mime, mime, n);
1351 8 : out->document_mime[n] = '\0';
1352 : }
1353 9 : free(mime);
1354 :
1355 9 : if (r->len - r->pos < 8) return -1;
1356 9 : int64_t size = tl_read_int64(r);
1357 9 : if (out) out->document_size = size;
1358 :
1359 9 : if (flags & (1u << 0)) {
1360 0 : if (tl_skip_photo_size_vector(r) != 0) return -1;
1361 : }
1362 9 : if (flags & (1u << 1)) {
1363 0 : if (skip_video_size_vector(r) != 0) return -1;
1364 : }
1365 :
1366 9 : if (r->len - r->pos < 4) return -1;
1367 9 : int32_t dc = tl_read_int32(r);
1368 9 : if (out) out->dc_id = dc;
1369 :
1370 : /* attributes:Vector<DocumentAttribute> */
1371 9 : if (r->len - r->pos < 8) return -1;
1372 9 : uint32_t vec_crc = tl_read_uint32(r);
1373 9 : if (vec_crc != TL_vector) return -1;
1374 9 : uint32_t n_attrs = tl_read_uint32(r);
1375 22 : for (uint32_t i = 0; i < n_attrs; i++) {
1376 13 : 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 9 : return 0;
1382 : }
1383 :
1384 3 : int tl_skip_document(TlReader *r) {
1385 3 : 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 7 : static int skip_geo_point(TlReader *r) {
1396 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1397 7 : uint32_t crc = tl_read_uint32(r);
1398 7 : if (crc == CRC_geoPointEmpty) return 0;
1399 2 : if (crc != CRC_geoPoint) {
1400 0 : logger_log(LOG_WARN, "skip_geo_point: unknown 0x%08x", crc);
1401 0 : return -1;
1402 : }
1403 2 : if (r->len - r->pos < 28) return -1;
1404 2 : uint32_t flags = tl_read_uint32(r);
1405 2 : tl_read_double(r); tl_read_double(r); tl_read_int64(r);
1406 2 : if (flags & 1u) {
1407 0 : if (r->len - r->pos < 4) return -1;
1408 0 : tl_read_int32(r);
1409 : }
1410 2 : 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 52 : static int skip_rich_text(TlReader *r) {
1426 52 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1427 52 : uint32_t crc = tl_read_uint32(r);
1428 52 : switch (crc) {
1429 36 : case CRC_textEmpty:
1430 36 : return 0;
1431 3 : case CRC_textPlain:
1432 3 : return tl_skip_string(r);
1433 6 : 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 6 : return skip_rich_text(r);
1442 1 : case CRC_textUrl: {
1443 1 : if (skip_rich_text(r) != 0) return -1;
1444 1 : if (tl_skip_string(r) != 0) return -1;
1445 1 : if (r->len - r->pos < 8) return -1;
1446 1 : tl_read_int64(r); /* webpage_id */
1447 1 : return 0;
1448 : }
1449 2 : case CRC_textEmail:
1450 : case CRC_textPhone: {
1451 2 : if (skip_rich_text(r) != 0) return -1;
1452 2 : return tl_skip_string(r);
1453 : }
1454 2 : case CRC_textConcat: {
1455 2 : if (r->len - r->pos < 8) return -1;
1456 2 : uint32_t vec = tl_read_uint32(r);
1457 2 : if (vec != TL_vector) return -1;
1458 2 : uint32_t n = tl_read_uint32(r);
1459 12 : for (uint32_t i = 0; i < n; i++) {
1460 10 : if (skip_rich_text(r) != 0) return -1;
1461 : }
1462 2 : return 0;
1463 : }
1464 1 : case CRC_textImage: {
1465 1 : if (r->len - r->pos < 16) return -1;
1466 1 : tl_read_int64(r); /* document_id */
1467 1 : tl_read_int32(r); /* w */
1468 1 : tl_read_int32(r); /* h */
1469 1 : return 0;
1470 : }
1471 1 : case CRC_textAnchor: {
1472 1 : if (skip_rich_text(r) != 0) return -1;
1473 1 : 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 8 : static int skip_page_caption(TlReader *r) {
1483 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1484 8 : uint32_t crc = tl_read_uint32(r);
1485 8 : if (crc != CRC_pageCaption) {
1486 0 : logger_log(LOG_WARN, "skip_page_caption: unknown 0x%08x", crc);
1487 0 : return -1;
1488 : }
1489 8 : if (skip_rich_text(r) != 0) return -1;
1490 8 : return skip_rich_text(r);
1491 : }
1492 :
1493 : /* Skip a Vector<PageBlock> — recursive into skip_page_block. */
1494 6 : static int skip_page_block_vector(TlReader *r) {
1495 6 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1496 6 : uint32_t vec = tl_read_uint32(r);
1497 6 : if (vec != TL_vector) return -1;
1498 6 : uint32_t n = tl_read_uint32(r);
1499 9 : for (uint32_t i = 0; i < n; i++) {
1500 3 : if (skip_page_block(r) != 0) return -1;
1501 : }
1502 6 : return 0;
1503 : }
1504 :
1505 : /* Skip a single PageListItem. */
1506 2 : static int skip_page_list_item(TlReader *r) {
1507 2 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1508 2 : uint32_t crc = tl_read_uint32(r);
1509 2 : switch (crc) {
1510 1 : case CRC_pageListItemText:
1511 1 : return skip_rich_text(r);
1512 1 : case CRC_pageListItemBlocks:
1513 1 : 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 2 : static int skip_page_list_ordered_item(TlReader *r) {
1521 2 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1522 2 : uint32_t crc = tl_read_uint32(r);
1523 2 : switch (crc) {
1524 1 : case CRC_pageListOrderedItemText:
1525 1 : if (tl_skip_string(r) != 0) return -1; /* num */
1526 1 : return skip_rich_text(r);
1527 1 : case CRC_pageListOrderedItemBlocks:
1528 1 : if (tl_skip_string(r) != 0) return -1; /* num */
1529 1 : 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 1 : static int skip_page_table_cell(TlReader *r) {
1540 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1541 1 : uint32_t crc = tl_read_uint32(r);
1542 1 : if (crc != CRC_pageTableCell) {
1543 0 : logger_log(LOG_WARN, "skip_page_table_cell: unknown 0x%08x", crc);
1544 0 : return -1;
1545 : }
1546 1 : if (r->len - r->pos < 4) return -1;
1547 1 : uint32_t flags = tl_read_uint32(r);
1548 1 : if (flags & (1u << 7)) {
1549 1 : if (skip_rich_text(r) != 0) return -1;
1550 : }
1551 1 : if (flags & (1u << 1)) {
1552 1 : if (r->len - r->pos < 4) return -1;
1553 1 : tl_read_int32(r); /* colspan */
1554 : }
1555 1 : if (flags & (1u << 2)) {
1556 1 : if (r->len - r->pos < 4) return -1;
1557 1 : tl_read_int32(r); /* rowspan */
1558 : }
1559 1 : return 0;
1560 : }
1561 :
1562 : /* pageTableRow#e0c0c5e5 cells:Vector<PageTableCell>. */
1563 1 : static int skip_page_table_row(TlReader *r) {
1564 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1565 1 : uint32_t crc = tl_read_uint32(r);
1566 1 : if (crc != CRC_pageTableRow) {
1567 0 : logger_log(LOG_WARN, "skip_page_table_row: unknown 0x%08x", crc);
1568 0 : return -1;
1569 : }
1570 1 : if (r->len - r->pos < 8) return -1;
1571 1 : uint32_t vec = tl_read_uint32(r);
1572 1 : if (vec != TL_vector) return -1;
1573 1 : uint32_t n = tl_read_uint32(r);
1574 2 : for (uint32_t i = 0; i < n; i++) {
1575 1 : if (skip_page_table_cell(r) != 0) return -1;
1576 : }
1577 1 : 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 1 : static int skip_page_related_article(TlReader *r) {
1585 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1586 1 : uint32_t crc = tl_read_uint32(r);
1587 1 : 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 1 : if (r->len - r->pos < 4) return -1;
1593 1 : uint32_t flags = tl_read_uint32(r);
1594 1 : if (tl_skip_string(r) != 0) return -1; /* url */
1595 1 : if (r->len - r->pos < 8) return -1;
1596 1 : tl_read_int64(r); /* webpage_id */
1597 1 : if (flags & (1u << 0)) if (tl_skip_string(r) != 0) return -1;
1598 1 : if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
1599 1 : if (flags & (1u << 2)) {
1600 1 : if (r->len - r->pos < 8) return -1;
1601 1 : tl_read_int64(r); /* photo_id */
1602 : }
1603 1 : if (flags & (1u << 3)) if (tl_skip_string(r) != 0) return -1;
1604 1 : if (flags & (1u << 4)) {
1605 1 : if (r->len - r->pos < 4) return -1;
1606 1 : tl_read_int32(r); /* published_date */
1607 : }
1608 1 : return 0;
1609 : }
1610 :
1611 : /* Skip a single PageBlock — full coverage of the PageBlock tree. */
1612 32 : static int skip_page_block(TlReader *r) {
1613 32 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1614 32 : uint32_t crc = tl_read_uint32(r);
1615 32 : switch (crc) {
1616 6 : case CRC_pageBlockUnsupported:
1617 : case CRC_pageBlockDivider:
1618 6 : return 0;
1619 4 : 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 4 : return skip_rich_text(r);
1627 1 : case CRC_pageBlockAnchor:
1628 1 : return tl_skip_string(r);
1629 1 : case CRC_pageBlockPreformatted: {
1630 1 : if (skip_rich_text(r) != 0) return -1;
1631 1 : return tl_skip_string(r); /* language */
1632 : }
1633 1 : case CRC_pageBlockAuthorDate: {
1634 1 : if (skip_rich_text(r) != 0) return -1; /* author */
1635 1 : if (r->len - r->pos < 4) return -1;
1636 1 : tl_read_int32(r); /* published_date */
1637 1 : return 0;
1638 : }
1639 2 : case CRC_pageBlockBlockquote:
1640 : case CRC_pageBlockPullquote: {
1641 2 : if (skip_rich_text(r) != 0) return -1; /* text */
1642 2 : return skip_rich_text(r); /* caption */
1643 : }
1644 1 : case CRC_pageBlockPhoto: {
1645 1 : if (r->len - r->pos < 4) return -1;
1646 1 : uint32_t flags = tl_read_uint32(r);
1647 1 : if (r->len - r->pos < 8) return -1;
1648 1 : tl_read_int64(r); /* photo_id */
1649 1 : if (skip_page_caption(r) != 0) return -1;
1650 1 : 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 1 : return 0;
1656 : }
1657 1 : case CRC_pageBlockVideo: {
1658 1 : if (r->len - r->pos < 12) return -1;
1659 1 : tl_read_uint32(r); /* flags (autoplay/loop only) */
1660 1 : tl_read_int64(r); /* video_id */
1661 1 : return skip_page_caption(r);
1662 : }
1663 1 : case CRC_pageBlockAudio: {
1664 1 : if (r->len - r->pos < 8) return -1;
1665 1 : tl_read_int64(r); /* audio_id */
1666 1 : return skip_page_caption(r);
1667 : }
1668 1 : case CRC_pageBlockCover:
1669 1 : return skip_page_block(r);
1670 1 : case CRC_pageBlockChannel:
1671 1 : return tl_skip_chat(r);
1672 1 : case CRC_pageBlockMap: {
1673 1 : if (skip_geo_point(r) != 0) return -1;
1674 1 : if (r->len - r->pos < 12) return -1;
1675 1 : tl_read_int32(r); /* zoom */
1676 1 : tl_read_int32(r); tl_read_int32(r); /* w, h */
1677 1 : return skip_page_caption(r);
1678 : }
1679 2 : case CRC_pageBlockList: {
1680 2 : if (r->len - r->pos < 8) return -1;
1681 2 : uint32_t vec = tl_read_uint32(r);
1682 2 : if (vec != TL_vector) return -1;
1683 2 : uint32_t n = tl_read_uint32(r);
1684 4 : for (uint32_t i = 0; i < n; i++) {
1685 2 : if (skip_page_list_item(r) != 0) return -1;
1686 : }
1687 2 : return 0;
1688 : }
1689 2 : case CRC_pageBlockOrderedList: {
1690 2 : if (r->len - r->pos < 8) return -1;
1691 2 : uint32_t vec = tl_read_uint32(r);
1692 2 : if (vec != TL_vector) return -1;
1693 2 : uint32_t n = tl_read_uint32(r);
1694 4 : for (uint32_t i = 0; i < n; i++) {
1695 2 : if (skip_page_list_ordered_item(r) != 0) return -1;
1696 : }
1697 2 : return 0;
1698 : }
1699 2 : case CRC_pageBlockCollage:
1700 : case CRC_pageBlockSlideshow: {
1701 2 : if (skip_page_block_vector(r) != 0) return -1;
1702 2 : return skip_page_caption(r);
1703 : }
1704 1 : case CRC_pageBlockDetails: {
1705 1 : if (r->len - r->pos < 4) return -1;
1706 1 : tl_read_uint32(r); /* flags (open only) */
1707 1 : if (skip_page_block_vector(r) != 0) return -1;
1708 1 : return skip_rich_text(r); /* title */
1709 : }
1710 1 : case CRC_pageBlockRelatedArticles: {
1711 1 : if (skip_rich_text(r) != 0) return -1; /* title */
1712 1 : if (r->len - r->pos < 8) return -1;
1713 1 : uint32_t vec = tl_read_uint32(r);
1714 1 : if (vec != TL_vector) return -1;
1715 1 : uint32_t n = tl_read_uint32(r);
1716 2 : for (uint32_t i = 0; i < n; i++) {
1717 1 : if (skip_page_related_article(r) != 0) return -1;
1718 : }
1719 1 : return 0;
1720 : }
1721 1 : case CRC_pageBlockTable: {
1722 1 : if (r->len - r->pos < 4) return -1;
1723 1 : tl_read_uint32(r); /* flags (bordered/striped) */
1724 1 : if (skip_rich_text(r) != 0) return -1; /* title */
1725 1 : if (r->len - r->pos < 8) return -1;
1726 1 : uint32_t vec = tl_read_uint32(r);
1727 1 : if (vec != TL_vector) return -1;
1728 1 : uint32_t n = tl_read_uint32(r);
1729 2 : for (uint32_t i = 0; i < n; i++) {
1730 1 : if (skip_page_table_row(r) != 0) return -1;
1731 : }
1732 1 : return 0;
1733 : }
1734 1 : case CRC_pageBlockEmbed: {
1735 1 : if (r->len - r->pos < 4) return -1;
1736 1 : uint32_t flags = tl_read_uint32(r);
1737 1 : if (flags & (1u << 1)) if (tl_skip_string(r) != 0) return -1;
1738 1 : if (flags & (1u << 2)) if (tl_skip_string(r) != 0) return -1;
1739 1 : 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 1 : 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 1 : return skip_page_caption(r);
1748 : }
1749 1 : case CRC_pageBlockEmbedPost: {
1750 1 : if (tl_skip_string(r) != 0) return -1; /* url */
1751 1 : if (r->len - r->pos < 16) return -1;
1752 1 : tl_read_int64(r); /* webpage_id */
1753 1 : tl_read_int64(r); /* author_photo_id */
1754 1 : if (tl_skip_string(r) != 0) return -1; /* author */
1755 1 : if (r->len - r->pos < 4) return -1;
1756 1 : tl_read_int32(r); /* date */
1757 1 : if (skip_page_block_vector(r) != 0) return -1;
1758 1 : return skip_page_caption(r);
1759 : }
1760 0 : default:
1761 0 : logger_log(LOG_WARN, "skip_page_block: unsupported 0x%08x", crc);
1762 0 : 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 5 : static int skip_page(TlReader *r) {
1770 5 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1771 5 : uint32_t crc = tl_read_uint32(r);
1772 5 : if (crc != CRC_page) {
1773 0 : logger_log(LOG_WARN, "skip_page: unknown Page variant 0x%08x", crc);
1774 0 : return -1;
1775 : }
1776 5 : if (r->len - r->pos < 4) return -1;
1777 5 : uint32_t flags = tl_read_uint32(r);
1778 5 : if (tl_skip_string(r) != 0) return -1; /* url */
1779 :
1780 : /* blocks:Vector<PageBlock> */
1781 5 : if (r->len - r->pos < 8) return -1;
1782 5 : uint32_t vec = tl_read_uint32(r);
1783 5 : if (vec != TL_vector) return -1;
1784 5 : uint32_t n = tl_read_uint32(r);
1785 33 : for (uint32_t i = 0; i < n; i++) {
1786 28 : if (skip_page_block(r) != 0) return -1;
1787 : }
1788 :
1789 : /* photos:Vector<Photo> */
1790 5 : if (r->len - r->pos < 8) return -1;
1791 5 : vec = tl_read_uint32(r);
1792 5 : if (vec != TL_vector) return -1;
1793 5 : n = tl_read_uint32(r);
1794 5 : 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 5 : if (r->len - r->pos < 8) return -1;
1800 5 : vec = tl_read_uint32(r);
1801 5 : if (vec != TL_vector) return -1;
1802 5 : n = tl_read_uint32(r);
1803 5 : 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 5 : if (flags & (1u << 3)) {
1809 0 : if (r->len - r->pos < 4) return -1;
1810 0 : tl_read_int32(r);
1811 : }
1812 5 : 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 1 : static int skip_webpage_attributes_vector(TlReader *r) {
1819 1 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
1820 1 : uint32_t vec = tl_read_uint32(r);
1821 1 : if (vec != TL_vector) return -1;
1822 1 : uint32_t n = tl_read_uint32(r);
1823 3 : for (uint32_t i = 0; i < n; i++) {
1824 2 : if (r->len - r->pos < 4) return -1;
1825 2 : uint32_t crc = tl_read_uint32(r);
1826 2 : switch (crc) {
1827 1 : 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 1 : if (r->len - r->pos < 4) return -1;
1833 1 : uint32_t flags = tl_read_uint32(r);
1834 1 : if (flags & (1u << 0)) {
1835 0 : if (r->len - r->pos < 8) return -1;
1836 0 : uint32_t dvec = tl_read_uint32(r);
1837 0 : if (dvec != TL_vector) return -1;
1838 0 : uint32_t dn = tl_read_uint32(r);
1839 0 : for (uint32_t j = 0; j < dn; j++) {
1840 0 : if (tl_skip_document(r) != 0) return -1;
1841 : }
1842 : }
1843 1 : if (flags & (1u << 1)) {
1844 0 : logger_log(LOG_WARN,
1845 : "skip_webpage_attributes: theme settings not supported");
1846 0 : return -1;
1847 : }
1848 1 : break;
1849 : }
1850 0 : case CRC_webPageAttributeStory: {
1851 : /* flags:# peer:Peer id:int story:flags.0?StoryItem */
1852 0 : if (r->len - r->pos < 4) return -1;
1853 0 : uint32_t flags = tl_read_uint32(r);
1854 0 : if (tl_skip_peer(r) != 0) return -1;
1855 0 : if (r->len - r->pos < 4) return -1;
1856 0 : tl_read_int32(r); /* id */
1857 0 : if (flags & (1u << 0)) {
1858 0 : if (skip_story_item(r) != 0) return -1;
1859 : }
1860 0 : break;
1861 : }
1862 1 : case CRC_webPageAttributeStickerSet: {
1863 : /* flags:# emojis:flags.0?true text_color:flags.1?true
1864 : * stickers:Vector<Document> */
1865 1 : if (r->len - r->pos < 4) return -1;
1866 1 : (void)tl_read_uint32(r); /* flags */
1867 1 : if (r->len - r->pos < 8) return -1;
1868 1 : uint32_t svec = tl_read_uint32(r);
1869 1 : if (svec != TL_vector) return -1;
1870 1 : uint32_t sn = tl_read_uint32(r);
1871 1 : for (uint32_t j = 0; j < sn; j++) {
1872 0 : if (tl_skip_document(r) != 0) return -1;
1873 : }
1874 1 : break;
1875 : }
1876 0 : default:
1877 0 : logger_log(LOG_WARN,
1878 : "skip_webpage_attributes: unknown variant 0x%08x", crc);
1879 0 : return -1;
1880 : }
1881 : }
1882 1 : return 0;
1883 : }
1884 :
1885 : /* Skip a WebPage variant. */
1886 12 : static int skip_webpage(TlReader *r) {
1887 12 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1888 12 : uint32_t crc = tl_read_uint32(r);
1889 12 : switch (crc) {
1890 1 : case CRC_webPageEmpty: {
1891 1 : if (r->len - r->pos < 12) return -1;
1892 1 : uint32_t flags = tl_read_uint32(r);
1893 1 : tl_read_int64(r); /* id */
1894 1 : if (flags & (1u << 0))
1895 0 : if (tl_skip_string(r) != 0) return -1;
1896 1 : return 0;
1897 : }
1898 1 : case CRC_webPagePending: {
1899 1 : if (r->len - r->pos < 12) return -1;
1900 1 : uint32_t flags = tl_read_uint32(r);
1901 1 : tl_read_int64(r);
1902 1 : if (flags & (1u << 0))
1903 0 : if (tl_skip_string(r) != 0) return -1;
1904 1 : if (r->len - r->pos < 4) return -1;
1905 1 : tl_read_int32(r); /* date */
1906 1 : return 0;
1907 : }
1908 1 : case CRC_webPageNotModified: {
1909 1 : if (r->len - r->pos < 4) return -1;
1910 1 : uint32_t flags = tl_read_uint32(r);
1911 1 : if (flags & (1u << 0)) {
1912 1 : if (r->len - r->pos < 4) return -1;
1913 1 : tl_read_int32(r); /* cached_page_views */
1914 : }
1915 1 : return 0;
1916 : }
1917 8 : 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 8 : if (r->len - r->pos < 16) return -1;
1928 8 : uint32_t flags = tl_read_uint32(r);
1929 8 : tl_read_int64(r); /* id */
1930 8 : if (tl_skip_string(r) != 0) return -1; /* url */
1931 8 : if (tl_skip_string(r) != 0) return -1; /* display_url */
1932 8 : if (r->len - r->pos < 4) return -1;
1933 8 : tl_read_int32(r); /* hash */
1934 8 : if (flags & (1u << 0))
1935 1 : if (tl_skip_string(r) != 0) return -1;
1936 8 : if (flags & (1u << 1))
1937 1 : if (tl_skip_string(r) != 0) return -1;
1938 8 : if (flags & (1u << 2))
1939 1 : if (tl_skip_string(r) != 0) return -1;
1940 8 : if (flags & (1u << 3))
1941 1 : if (tl_skip_string(r) != 0) return -1;
1942 8 : if (flags & (1u << 4)) {
1943 0 : if (photo_full(r, NULL) != 0) return -1;
1944 : }
1945 8 : if (flags & (1u << 5)) {
1946 1 : if (tl_skip_string(r) != 0) return -1;
1947 1 : if (tl_skip_string(r) != 0) return -1;
1948 : }
1949 8 : if (flags & (1u << 6)) {
1950 1 : if (r->len - r->pos < 8) return -1;
1951 1 : tl_read_int32(r); tl_read_int32(r);
1952 : }
1953 8 : if (flags & (1u << 7)) {
1954 1 : if (r->len - r->pos < 4) return -1;
1955 1 : tl_read_int32(r);
1956 : }
1957 8 : if (flags & (1u << 8))
1958 1 : if (tl_skip_string(r) != 0) return -1;
1959 8 : if (flags & (1u << 9)) {
1960 : /* document:Document — reuse tl_skip_document. */
1961 0 : if (tl_skip_document(r) != 0) return -1;
1962 : }
1963 8 : if (flags & (1u << 10)) {
1964 5 : if (skip_page(r) != 0) return -1;
1965 : }
1966 8 : if (flags & (1u << 12)) {
1967 1 : if (skip_webpage_attributes_vector(r) != 0) return -1;
1968 : }
1969 8 : return 0;
1970 : }
1971 1 : default:
1972 1 : logger_log(LOG_WARN, "skip_webpage: unknown 0x%08x", crc);
1973 1 : return -1;
1974 : }
1975 : }
1976 :
1977 : /* Skip a textWithEntities#751f3146: text:string + Vector<MessageEntity>. */
1978 7 : static int skip_text_with_entities(TlReader *r) {
1979 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1980 7 : uint32_t crc = tl_read_uint32(r);
1981 7 : 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 7 : if (tl_skip_string(r) != 0) return -1;
1986 7 : return tl_skip_message_entities_vector(r);
1987 : }
1988 :
1989 : /* Skip a Poll#58747131 object. */
1990 4 : static int skip_poll(TlReader *r) {
1991 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
1992 4 : uint32_t crc = tl_read_uint32(r);
1993 4 : if (crc != CRC_poll) {
1994 0 : logger_log(LOG_WARN, "skip_poll: unknown 0x%08x", crc);
1995 0 : return -1;
1996 : }
1997 4 : if (r->len - r->pos < 12) return -1;
1998 4 : uint32_t flags = tl_read_uint32(r);
1999 4 : tl_read_int64(r); /* id */
2000 4 : if (skip_text_with_entities(r) != 0) return -1; /* question */
2001 :
2002 : /* answers:Vector<PollAnswer> */
2003 4 : if (r->len - r->pos < 8) return -1;
2004 4 : uint32_t vec_crc = tl_read_uint32(r);
2005 4 : if (vec_crc != TL_vector) return -1;
2006 4 : uint32_t n_answers = tl_read_uint32(r);
2007 7 : for (uint32_t i = 0; i < n_answers; i++) {
2008 3 : if (r->len - r->pos < 4) return -1;
2009 3 : uint32_t pa_crc = tl_read_uint32(r);
2010 3 : 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 3 : if (skip_text_with_entities(r) != 0) return -1;
2015 3 : if (tl_skip_string(r) != 0) return -1; /* option:bytes */
2016 : }
2017 4 : if (flags & (1u << 4)) {
2018 0 : if (r->len - r->pos < 4) return -1;
2019 0 : tl_read_int32(r); /* close_period */
2020 : }
2021 4 : if (flags & (1u << 5)) {
2022 0 : if (r->len - r->pos < 4) return -1;
2023 0 : tl_read_int32(r); /* close_date */
2024 : }
2025 4 : return 0;
2026 : }
2027 :
2028 : /* Skip a PollAnswerVoters#3b6ddad2. */
2029 3 : static int skip_poll_answer_voters(TlReader *r) {
2030 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2031 3 : uint32_t crc = tl_read_uint32(r);
2032 3 : if (crc != CRC_pollAnswerVoters) {
2033 1 : logger_log(LOG_WARN, "skip_poll_answer_voters: 0x%08x", crc);
2034 1 : return -1;
2035 : }
2036 2 : if (r->len - r->pos < 4) return -1;
2037 2 : tl_read_uint32(r); /* flags */
2038 2 : if (tl_skip_string(r) != 0) return -1; /* option:bytes */
2039 2 : if (r->len - r->pos < 4) return -1;
2040 2 : tl_read_int32(r); /* voters */
2041 2 : return 0;
2042 : }
2043 :
2044 : /* Skip a PollResults#7adc669d object. */
2045 4 : static int skip_poll_results(TlReader *r) {
2046 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2047 4 : uint32_t crc = tl_read_uint32(r);
2048 4 : if (crc != CRC_pollResults) {
2049 0 : logger_log(LOG_WARN, "skip_poll_results: 0x%08x", crc);
2050 0 : return -1;
2051 : }
2052 4 : if (r->len - r->pos < 4) return -1;
2053 4 : uint32_t flags = tl_read_uint32(r);
2054 4 : if (flags & (1u << 1)) {
2055 2 : if (r->len - r->pos < 8) return -1;
2056 2 : uint32_t vc = tl_read_uint32(r);
2057 2 : if (vc != TL_vector) return -1;
2058 2 : uint32_t n = tl_read_uint32(r);
2059 4 : for (uint32_t i = 0; i < n; i++) {
2060 3 : if (skip_poll_answer_voters(r) != 0) return -1;
2061 : }
2062 : }
2063 3 : if (flags & (1u << 2)) {
2064 1 : if (r->len - r->pos < 4) return -1;
2065 1 : tl_read_int32(r); /* total_voters */
2066 : }
2067 3 : if (flags & (1u << 3)) {
2068 1 : if (r->len - r->pos < 8) return -1;
2069 1 : uint32_t vc = tl_read_uint32(r);
2070 1 : if (vc != TL_vector) return -1;
2071 1 : uint32_t n = tl_read_uint32(r);
2072 2 : for (uint32_t i = 0; i < n; i++) {
2073 1 : if (tl_skip_peer(r) != 0) return -1;
2074 : }
2075 : }
2076 3 : if (flags & (1u << 4)) {
2077 1 : if (tl_skip_string(r) != 0) return -1; /* solution */
2078 1 : if (tl_skip_message_entities_vector(r) != 0) return -1;
2079 : }
2080 3 : 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 3 : static int skip_game(TlReader *r) {
2088 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2089 3 : uint32_t crc = tl_read_uint32(r);
2090 3 : if (crc != CRC_game) {
2091 1 : logger_log(LOG_WARN, "skip_game: unexpected 0x%08x", crc);
2092 1 : return -1;
2093 : }
2094 2 : if (r->len - r->pos < 4 + 8 + 8) return -1;
2095 2 : uint32_t flags = tl_read_uint32(r);
2096 2 : tl_read_int64(r); /* id */
2097 2 : tl_read_int64(r); /* access_hash */
2098 2 : if (tl_skip_string(r) != 0) return -1; /* short_name */
2099 2 : if (tl_skip_string(r) != 0) return -1; /* title */
2100 2 : if (tl_skip_string(r) != 0) return -1; /* description */
2101 2 : if (tl_skip_photo(r) != 0) return -1; /* photo */
2102 2 : if (flags & (1u << 0)) {
2103 1 : if (tl_skip_document(r) != 0) return -1; /* document */
2104 : }
2105 2 : 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 4 : static int skip_message_extended_media(TlReader *r) {
2114 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2115 4 : uint32_t crc = tl_read_uint32(r);
2116 4 : switch (crc) {
2117 2 : case CRC_messageExtendedMediaPreview: {
2118 2 : if (r->len - r->pos < 4) return -1;
2119 2 : uint32_t flags = tl_read_uint32(r);
2120 2 : if (flags & (1u << 0)) {
2121 1 : if (r->len - r->pos < 8) return -1;
2122 1 : tl_read_int32(r); tl_read_int32(r); /* w, h */
2123 : }
2124 2 : if (flags & (1u << 1)) {
2125 1 : if (tl_skip_photo_size(r) != 0) return -1;
2126 : }
2127 2 : if (flags & (1u << 2)) {
2128 1 : if (r->len - r->pos < 4) return -1;
2129 1 : tl_read_int32(r); /* video_duration */
2130 : }
2131 2 : return 0;
2132 : }
2133 1 : case CRC_messageExtendedMedia:
2134 : /* Recurse into the wrapped MessageMedia. The inner metadata is
2135 : * discarded — paid-media iteration only needs the outer kind. */
2136 1 : return tl_skip_message_media_ex(r, NULL);
2137 1 : default:
2138 1 : logger_log(LOG_WARN,
2139 : "skip_message_extended_media: unknown 0x%08x", crc);
2140 1 : 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 3 : static int skip_web_document(TlReader *r) {
2151 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2152 3 : uint32_t crc = tl_read_uint32(r);
2153 : int has_access_hash;
2154 3 : switch (crc) {
2155 1 : case CRC_webDocument: has_access_hash = 1; break;
2156 1 : case CRC_webDocumentNoProxy: has_access_hash = 0; break;
2157 1 : default:
2158 1 : logger_log(LOG_WARN, "skip_web_document: unknown 0x%08x", crc);
2159 1 : return -1;
2160 : }
2161 2 : if (tl_skip_string(r) != 0) return -1; /* url */
2162 2 : if (has_access_hash) {
2163 1 : if (r->len - r->pos < 8) return -1;
2164 1 : tl_read_int64(r); /* access_hash */
2165 : }
2166 2 : if (r->len - r->pos < 4) return -1;
2167 2 : tl_read_int32(r); /* size */
2168 2 : if (tl_skip_string(r) != 0) return -1; /* mime_type */
2169 : /* attributes:Vector<DocumentAttribute> */
2170 2 : if (r->len - r->pos < 8) return -1;
2171 2 : uint32_t vc = tl_read_uint32(r);
2172 2 : if (vc != TL_vector) return -1;
2173 2 : uint32_t n = tl_read_uint32(r);
2174 3 : for (uint32_t i = 0; i < n; i++) {
2175 1 : if (skip_document_attribute(r, NULL, 0) != 0) return -1;
2176 : }
2177 2 : 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 2 : static int skip_story_fwd_header(TlReader *r) {
2185 2 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2186 2 : uint32_t crc = tl_read_uint32(r);
2187 2 : if (crc != CRC_storyFwdHeader) {
2188 0 : logger_log(LOG_WARN, "skip_story_fwd_header: unknown 0x%08x", crc);
2189 0 : return -1;
2190 : }
2191 2 : if (r->len - r->pos < 4) return -1;
2192 2 : uint32_t flags = tl_read_uint32(r);
2193 2 : if (flags & (1u << 0))
2194 1 : if (tl_skip_peer(r) != 0) return -1; /* from */
2195 2 : if (flags & (1u << 1))
2196 1 : if (tl_skip_string(r) != 0) return -1; /* from_name */
2197 2 : if (flags & (1u << 2)) {
2198 2 : if (r->len - r->pos < 4) return -1;
2199 2 : tl_read_int32(r); /* story_id */
2200 : }
2201 2 : 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 7 : static int skip_media_area_coordinates(TlReader *r) {
2209 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2210 7 : uint32_t crc = tl_read_uint32(r);
2211 7 : 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 7 : if (r->len - r->pos < 4 + 5 * 8) return -1;
2217 7 : uint32_t flags = tl_read_uint32(r);
2218 42 : for (int i = 0; i < 5; i++) tl_read_double(r); /* x, y, w, h, rot */
2219 7 : if (flags & (1u << 0)) {
2220 0 : if (r->len - r->pos < 8) return -1;
2221 0 : tl_read_double(r); /* radius */
2222 : }
2223 7 : 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 1 : static int skip_geo_point_address(TlReader *r) {
2231 1 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2232 1 : uint32_t crc = tl_read_uint32(r);
2233 1 : 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 1 : if (r->len - r->pos < 4) return -1;
2239 1 : uint32_t flags = tl_read_uint32(r);
2240 1 : if (tl_skip_string(r) != 0) return -1; /* country_iso2 */
2241 1 : if (flags & (1u << 0))
2242 1 : if (tl_skip_string(r) != 0) return -1; /* state */
2243 1 : if (flags & (1u << 1))
2244 1 : if (tl_skip_string(r) != 0) return -1; /* city */
2245 1 : if (flags & (1u << 2))
2246 1 : if (tl_skip_string(r) != 0) return -1; /* street */
2247 1 : return 0;
2248 : }
2249 :
2250 : /* ---- MediaArea ---- dispatch over 7 known variants. */
2251 8 : static int skip_media_area(TlReader *r) {
2252 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2253 8 : uint32_t crc = tl_read_uint32(r);
2254 8 : switch (crc) {
2255 1 : case CRC_mediaAreaVenue:
2256 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2257 1 : if (skip_geo_point(r) != 0) return -1;
2258 1 : if (tl_skip_string(r) != 0) return -1; /* title */
2259 1 : if (tl_skip_string(r) != 0) return -1; /* address */
2260 1 : if (tl_skip_string(r) != 0) return -1; /* provider */
2261 1 : if (tl_skip_string(r) != 0) return -1; /* venue_id */
2262 1 : if (tl_skip_string(r) != 0) return -1; /* venue_type */
2263 1 : return 0;
2264 1 : case CRC_mediaAreaGeoPoint: {
2265 1 : if (r->len - r->pos < 4) return -1;
2266 1 : uint32_t flags = tl_read_uint32(r);
2267 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2268 1 : if (skip_geo_point(r) != 0) return -1;
2269 1 : if (flags & (1u << 0))
2270 1 : if (skip_geo_point_address(r) != 0) return -1;
2271 1 : return 0;
2272 : }
2273 1 : case CRC_mediaAreaSuggestedReaction: {
2274 1 : if (r->len - r->pos < 4) return -1;
2275 1 : tl_read_uint32(r); /* flags */
2276 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2277 1 : return skip_reaction(r);
2278 : }
2279 1 : case CRC_mediaAreaChannelPost:
2280 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2281 1 : if (r->len - r->pos < 12) return -1;
2282 1 : tl_read_int64(r); /* channel_id */
2283 1 : tl_read_int32(r); /* msg_id */
2284 1 : return 0;
2285 1 : case CRC_mediaAreaUrl:
2286 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2287 1 : return tl_skip_string(r); /* url */
2288 1 : case CRC_mediaAreaWeather:
2289 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2290 1 : if (tl_skip_string(r) != 0) return -1; /* emoji */
2291 1 : if (r->len - r->pos < 8 + 4) return -1;
2292 1 : tl_read_double(r); /* temperature_c */
2293 1 : tl_read_int32(r); /* color */
2294 1 : return 0;
2295 1 : case CRC_mediaAreaStarGift:
2296 1 : if (skip_media_area_coordinates(r) != 0) return -1;
2297 1 : return tl_skip_string(r); /* slug */
2298 1 : default:
2299 1 : logger_log(LOG_WARN, "skip_media_area: unknown 0x%08x", crc);
2300 1 : return -1;
2301 : }
2302 : }
2303 :
2304 : /* ---- PrivacyRule ---- 12 variants; most are CRC-only. */
2305 3 : static int skip_privacy_rule(TlReader *r) {
2306 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2307 3 : uint32_t crc = tl_read_uint32(r);
2308 3 : switch (crc) {
2309 1 : 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 1 : return 0;
2318 1 : case CRC_privacyValueAllowUsers:
2319 : case CRC_privacyValueDisallowUsers:
2320 : case CRC_privacyValueAllowChatParticipants:
2321 : case CRC_privacyValueDisallowChatParticipants: {
2322 : /* Vector<long> */
2323 1 : if (r->len - r->pos < 8) return -1;
2324 1 : uint32_t vc = tl_read_uint32(r);
2325 1 : if (vc != TL_vector) return -1;
2326 1 : uint32_t n = tl_read_uint32(r);
2327 1 : if (r->len - r->pos < (size_t)n * 8) return -1;
2328 3 : for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
2329 1 : return 0;
2330 : }
2331 1 : default:
2332 1 : logger_log(LOG_WARN, "skip_privacy_rule: unknown 0x%08x", crc);
2333 1 : 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 2 : static int skip_story_views(TlReader *r) {
2343 2 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2344 2 : uint32_t crc = tl_read_uint32(r);
2345 2 : if (crc != CRC_storyViews) {
2346 1 : logger_log(LOG_WARN, "skip_story_views: unknown 0x%08x", crc);
2347 1 : return -1;
2348 : }
2349 1 : if (r->len - r->pos < 4) return -1;
2350 1 : uint32_t flags = tl_read_uint32(r);
2351 1 : if (r->len - r->pos < 4) return -1;
2352 1 : tl_read_int32(r); /* views_count */
2353 1 : if (flags & (1u << 2)) {
2354 1 : if (r->len - r->pos < 4) return -1;
2355 1 : tl_read_int32(r); /* forwards_count */
2356 : }
2357 1 : if (flags & (1u << 3)) {
2358 1 : if (r->len - r->pos < 8) return -1;
2359 1 : uint32_t vc = tl_read_uint32(r);
2360 1 : if (vc != TL_vector) return -1;
2361 1 : uint32_t n = tl_read_uint32(r);
2362 2 : for (uint32_t i = 0; i < n; i++)
2363 1 : if (skip_reaction_count(r) != 0) return -1;
2364 : }
2365 1 : if (flags & (1u << 4)) {
2366 1 : if (r->len - r->pos < 4) return -1;
2367 1 : tl_read_int32(r); /* reactions_count */
2368 : }
2369 1 : if (flags & (1u << 0)) {
2370 1 : if (r->len - r->pos < 8) return -1;
2371 1 : uint32_t vc = tl_read_uint32(r);
2372 1 : if (vc != TL_vector) return -1;
2373 1 : uint32_t n = tl_read_uint32(r);
2374 1 : if (r->len - r->pos < (size_t)n * 8) return -1;
2375 2 : for (uint32_t i = 0; i < n; i++) tl_read_int64(r);
2376 : }
2377 1 : 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 10 : static int skip_story_item(TlReader *r) {
2391 10 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2392 10 : uint32_t crc = tl_read_uint32(r);
2393 10 : switch (crc) {
2394 1 : case CRC_storyItemDeleted:
2395 1 : if (r->len - r->pos < 4) return -1;
2396 1 : tl_read_int32(r); /* id */
2397 1 : return 0;
2398 1 : case CRC_storyItemSkipped: {
2399 1 : if (r->len - r->pos < 4) return -1;
2400 1 : tl_read_uint32(r); /* flags */
2401 1 : if (r->len - r->pos < 12) return -1;
2402 1 : tl_read_int32(r); /* id */
2403 1 : tl_read_int32(r); /* date */
2404 1 : tl_read_int32(r); /* expire_date */
2405 1 : return 0;
2406 : }
2407 7 : case CRC_storyItem: {
2408 7 : if (r->len - r->pos < 4) return -1;
2409 7 : uint32_t flags = tl_read_uint32(r);
2410 7 : if (r->len - r->pos < 8) return -1;
2411 7 : tl_read_int32(r); /* id */
2412 7 : tl_read_int32(r); /* date */
2413 7 : if (flags & (1u << 18))
2414 0 : if (tl_skip_peer(r) != 0) return -1; /* from_id */
2415 7 : if (flags & (1u << 17))
2416 2 : if (skip_story_fwd_header(r) != 0) return -1; /* fwd_from */
2417 7 : if (r->len - r->pos < 4) return -1;
2418 7 : tl_read_int32(r); /* expire_date */
2419 7 : if (flags & (1u << 0))
2420 1 : if (tl_skip_string(r) != 0) return -1; /* caption */
2421 7 : if (flags & (1u << 1))
2422 1 : if (tl_skip_message_entities_vector(r) != 0) return -1;
2423 7 : if (tl_skip_message_media_ex(r, NULL) != 0) return -1;
2424 7 : if (flags & (1u << 14)) {
2425 4 : if (r->len - r->pos < 8) return -1;
2426 4 : uint32_t vc = tl_read_uint32(r);
2427 4 : if (vc != TL_vector) return -1;
2428 4 : uint32_t n = tl_read_uint32(r);
2429 11 : for (uint32_t i = 0; i < n; i++)
2430 8 : if (skip_media_area(r) != 0) return -1;
2431 : }
2432 6 : if (flags & (1u << 2)) {
2433 2 : if (r->len - r->pos < 8) return -1;
2434 2 : uint32_t vc = tl_read_uint32(r);
2435 2 : if (vc != TL_vector) return -1;
2436 2 : uint32_t n = tl_read_uint32(r);
2437 4 : for (uint32_t i = 0; i < n; i++)
2438 3 : if (skip_privacy_rule(r) != 0) return -1;
2439 : }
2440 5 : if (flags & (1u << 3))
2441 2 : if (skip_story_views(r) != 0) return -1;
2442 4 : if (flags & (1u << 15))
2443 0 : if (skip_reaction(r) != 0) return -1; /* sent_reaction */
2444 4 : return 0;
2445 : }
2446 1 : default:
2447 1 : logger_log(LOG_WARN, "skip_story_item: unknown 0x%08x", crc);
2448 1 : 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 68 : int tl_skip_message_media_ex(TlReader *r, MediaInfo *out) {
2468 : static int recursion_depth = 0;
2469 68 : if (recursion_depth >= MEDIA_RECURSION_MAX) {
2470 0 : logger_log(LOG_WARN,
2471 : "tl_skip_message_media_ex: recursion depth limit (%d) reached",
2472 : MEDIA_RECURSION_MAX);
2473 0 : return -1;
2474 : }
2475 68 : recursion_depth++;
2476 68 : int rc = skip_message_media_body(r, out);
2477 68 : recursion_depth--;
2478 68 : return rc;
2479 : }
2480 :
2481 68 : static int skip_message_media_body(TlReader *r, MediaInfo *out) {
2482 68 : if (out) memset(out, 0, sizeof(*out));
2483 68 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2484 68 : uint32_t crc = tl_read_uint32(r);
2485 68 : switch (crc) {
2486 12 : case CRC_messageMediaEmpty:
2487 12 : if (out) out->kind = MEDIA_EMPTY;
2488 12 : return 0;
2489 1 : case CRC_messageMediaUnsupported:
2490 1 : if (out) out->kind = MEDIA_UNSUPPORTED;
2491 1 : return 0;
2492 :
2493 2 : case CRC_messageMediaGeo:
2494 2 : if (out) out->kind = MEDIA_GEO;
2495 2 : return skip_geo_point(r);
2496 :
2497 2 : case CRC_messageMediaContact:
2498 2 : if (out) out->kind = MEDIA_CONTACT;
2499 2 : if (tl_skip_string(r) != 0) return -1;
2500 2 : if (tl_skip_string(r) != 0) return -1;
2501 2 : if (tl_skip_string(r) != 0) return -1;
2502 2 : if (tl_skip_string(r) != 0) return -1;
2503 2 : if (r->len - r->pos < 8) return -1;
2504 2 : tl_read_int64(r);
2505 2 : return 0;
2506 :
2507 1 : case CRC_messageMediaVenue:
2508 1 : if (out) out->kind = MEDIA_VENUE;
2509 1 : if (skip_geo_point(r) != 0) return -1;
2510 1 : if (tl_skip_string(r) != 0) return -1;
2511 1 : if (tl_skip_string(r) != 0) return -1;
2512 1 : if (tl_skip_string(r) != 0) return -1;
2513 1 : if (tl_skip_string(r) != 0) return -1;
2514 1 : if (tl_skip_string(r) != 0) return -1;
2515 1 : return 0;
2516 :
2517 1 : case CRC_messageMediaGeoLive: {
2518 1 : if (out) out->kind = MEDIA_GEO_LIVE;
2519 1 : if (r->len - r->pos < 4) return -1;
2520 1 : uint32_t flags = tl_read_uint32(r);
2521 1 : if (skip_geo_point(r) != 0) return -1;
2522 1 : if (flags & 1u) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2523 1 : if (r->len - r->pos < 4) return -1;
2524 1 : tl_read_int32(r);
2525 1 : if (flags & (1u << 1)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2526 1 : return 0;
2527 : }
2528 :
2529 1 : case CRC_messageMediaDice:
2530 1 : if (out) out->kind = MEDIA_DICE;
2531 1 : if (r->len - r->pos < 4) return -1;
2532 1 : tl_read_int32(r);
2533 1 : return tl_skip_string(r);
2534 :
2535 2 : case CRC_messageMediaPhoto: {
2536 2 : if (out) out->kind = MEDIA_PHOTO;
2537 2 : if (r->len - r->pos < 4) return -1;
2538 2 : uint32_t flags = tl_read_uint32(r);
2539 2 : if (flags & (1u << 0)) {
2540 2 : if (photo_full(r, out) != 0) return -1;
2541 : }
2542 2 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2543 2 : return 0;
2544 : }
2545 :
2546 12 : case CRC_messageMediaWebPage: {
2547 12 : if (out) out->kind = MEDIA_WEBPAGE;
2548 12 : if (r->len - r->pos < 4) return -1;
2549 12 : uint32_t flags = tl_read_uint32(r);
2550 : (void)flags; /* only boolean previews */
2551 12 : return skip_webpage(r);
2552 : }
2553 :
2554 4 : case CRC_messageMediaPoll: {
2555 4 : if (out) out->kind = MEDIA_POLL;
2556 4 : if (skip_poll(r) != 0) return -1;
2557 4 : return skip_poll_results(r);
2558 : }
2559 :
2560 3 : case CRC_messageMediaInvoice: {
2561 3 : if (out) out->kind = MEDIA_INVOICE;
2562 3 : if (r->len - r->pos < 4) return -1;
2563 3 : uint32_t flags = tl_read_uint32(r);
2564 3 : if (tl_skip_string(r) != 0) return -1; /* title */
2565 3 : if (tl_skip_string(r) != 0) return -1; /* description */
2566 3 : if (flags & (1u << 0)) {
2567 3 : if (skip_web_document(r) != 0) return -1;
2568 : }
2569 2 : 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 2 : if (tl_skip_string(r) != 0) return -1; /* currency */
2574 2 : if (r->len - r->pos < 8) return -1;
2575 2 : tl_read_int64(r); /* total_amount */
2576 2 : if (tl_skip_string(r) != 0) return -1; /* start_param */
2577 2 : if (flags & (1u << 4)) {
2578 0 : if (skip_message_extended_media(r) != 0) return -1;
2579 : }
2580 2 : return 0;
2581 : }
2582 :
2583 10 : case CRC_messageMediaStory: {
2584 10 : if (out) out->kind = MEDIA_STORY;
2585 10 : if (r->len - r->pos < 4) return -1;
2586 10 : uint32_t flags = tl_read_uint32(r);
2587 10 : if (tl_skip_peer(r) != 0) return -1; /* peer */
2588 10 : if (r->len - r->pos < 4) return -1;
2589 10 : tl_read_int32(r); /* id */
2590 10 : if (flags & (1u << 0)) {
2591 10 : if (skip_story_item(r) != 0) return -1;
2592 : }
2593 6 : return 0;
2594 : }
2595 :
2596 0 : case CRC_messageMediaGiveaway: {
2597 0 : if (out) out->kind = MEDIA_GIVEAWAY;
2598 0 : if (r->len - r->pos < 4) return -1;
2599 0 : uint32_t flags = tl_read_uint32(r);
2600 : /* channels:Vector<long> */
2601 0 : if (r->len - r->pos < 8) return -1;
2602 0 : uint32_t vc = tl_read_uint32(r);
2603 0 : if (vc != TL_vector) return -1;
2604 0 : uint32_t n_ch = tl_read_uint32(r);
2605 0 : if (r->len - r->pos < (size_t)n_ch * 8) return -1;
2606 0 : for (uint32_t i = 0; i < n_ch; i++) tl_read_int64(r);
2607 0 : if (flags & (1u << 1)) {
2608 0 : if (r->len - r->pos < 8) return -1;
2609 0 : uint32_t cvc = tl_read_uint32(r);
2610 0 : if (cvc != TL_vector) return -1;
2611 0 : uint32_t n_c = tl_read_uint32(r);
2612 0 : for (uint32_t i = 0; i < n_c; i++)
2613 0 : if (tl_skip_string(r) != 0) return -1;
2614 : }
2615 0 : if (flags & (1u << 3))
2616 0 : if (tl_skip_string(r) != 0) return -1;
2617 0 : if (r->len - r->pos < 4) return -1;
2618 0 : tl_read_int32(r); /* quantity */
2619 0 : if (flags & (1u << 4)) {
2620 0 : if (r->len - r->pos < 4) return -1;
2621 0 : tl_read_int32(r); /* months */
2622 : }
2623 0 : if (flags & (1u << 5)) {
2624 0 : if (r->len - r->pos < 8) return -1;
2625 0 : tl_read_int64(r); /* stars */
2626 : }
2627 0 : if (r->len - r->pos < 4) return -1;
2628 0 : tl_read_int32(r); /* until_date */
2629 0 : return 0;
2630 : }
2631 :
2632 3 : case CRC_messageMediaGame: {
2633 3 : if (out) out->kind = MEDIA_GAME;
2634 3 : return skip_game(r);
2635 : }
2636 :
2637 4 : case CRC_messageMediaPaidMedia: {
2638 4 : if (out) out->kind = MEDIA_PAID;
2639 4 : if (r->len - r->pos < 8) return -1;
2640 4 : tl_read_int64(r); /* stars_amount */
2641 4 : if (r->len - r->pos < 8) return -1;
2642 4 : uint32_t vc = tl_read_uint32(r);
2643 4 : if (vc != TL_vector) return -1;
2644 4 : uint32_t n = tl_read_uint32(r);
2645 7 : for (uint32_t i = 0; i < n; i++) {
2646 4 : if (skip_message_extended_media(r) != 0) return -1;
2647 : }
2648 3 : return 0;
2649 : }
2650 :
2651 8 : case CRC_messageMediaDocument: {
2652 8 : if (out) out->kind = MEDIA_DOCUMENT;
2653 8 : if (r->len - r->pos < 4) return -1;
2654 8 : uint32_t flags = tl_read_uint32(r);
2655 8 : 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 8 : if (flags & (1u << 0)) {
2662 8 : if (document_inner(r, out) != 0) return -1;
2663 : }
2664 8 : if (flags & (1u << 2)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2665 8 : return 0;
2666 : }
2667 :
2668 2 : default:
2669 2 : if (out) out->kind = MEDIA_OTHER;
2670 2 : logger_log(LOG_WARN, "tl_skip_message_media: unsupported 0x%08x", crc);
2671 2 : return -1;
2672 : }
2673 : }
2674 :
2675 9 : int tl_skip_message_media(TlReader *r) {
2676 9 : 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 9 : static int read_string_into(TlReader *r, char *out, size_t out_cap) {
2685 9 : char *s = tl_read_string(r);
2686 9 : if (!s) {
2687 0 : if (out_cap > 0) out[0] = '\0';
2688 0 : return -1;
2689 : }
2690 9 : if (out_cap > 0) {
2691 9 : size_t n = strlen(s);
2692 9 : if (n >= out_cap) n = out_cap - 1;
2693 9 : memcpy(out, s, n);
2694 9 : out[n] = '\0';
2695 : }
2696 9 : free(s);
2697 9 : return 0;
2698 : }
2699 :
2700 : /* Append `src` to `dst` (NUL-terminated), respecting dst_cap. */
2701 6 : static void str_append(char *dst, size_t dst_cap, const char *src) {
2702 6 : if (dst_cap == 0) return;
2703 6 : size_t cur = strlen(dst);
2704 6 : if (cur >= dst_cap - 1) return;
2705 6 : size_t room = dst_cap - 1 - cur;
2706 6 : size_t n = strlen(src);
2707 6 : if (n > room) n = room;
2708 6 : memcpy(dst + cur, src, n);
2709 6 : 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 8 : int tl_skip_chat_photo(TlReader *r) {
2718 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2719 8 : uint32_t crc = tl_read_uint32(r);
2720 8 : if (crc == CRC_chatPhotoEmpty) return 0;
2721 3 : if (crc != CRC_chatPhoto) {
2722 1 : logger_log(LOG_WARN, "tl_skip_chat_photo: unknown 0x%08x", crc);
2723 1 : return -1;
2724 : }
2725 2 : if (r->len - r->pos < 4) return -1;
2726 2 : uint32_t flags = tl_read_uint32(r);
2727 : /* photo_id:long */
2728 2 : if (r->len - r->pos < 8) return -1;
2729 2 : tl_read_int64(r);
2730 2 : if (flags & (1u << 1)) {
2731 1 : if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
2732 : }
2733 : /* dc_id:int */
2734 2 : if (r->len - r->pos < 4) return -1;
2735 2 : tl_read_int32(r);
2736 2 : 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 3 : int tl_skip_user_profile_photo(TlReader *r) {
2746 3 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2747 3 : uint32_t crc = tl_read_uint32(r);
2748 3 : if (crc == CRC_userProfilePhotoEmpty) return 0;
2749 2 : if (crc != CRC_userProfilePhoto) {
2750 1 : logger_log(LOG_WARN, "tl_skip_user_profile_photo: unknown 0x%08x", crc);
2751 1 : return -1;
2752 : }
2753 1 : if (r->len - r->pos < 4) return -1;
2754 1 : uint32_t flags = tl_read_uint32(r);
2755 : /* photo_id:long */
2756 1 : if (r->len - r->pos < 8) return -1;
2757 1 : tl_read_int64(r);
2758 1 : if (flags & (1u << 1)) {
2759 0 : if (tl_skip_string(r) != 0) return -1; /* stripped_thumb:bytes */
2760 : }
2761 : /* dc_id:int */
2762 1 : if (r->len - r->pos < 4) return -1;
2763 1 : tl_read_int32(r);
2764 1 : return 0;
2765 : }
2766 :
2767 : /* ---- UserStatus ---- */
2768 7 : int tl_skip_user_status(TlReader *r) {
2769 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2770 7 : uint32_t crc = tl_read_uint32(r);
2771 7 : switch (crc) {
2772 1 : case CRC_userStatusEmpty:
2773 1 : return 0;
2774 5 : case CRC_userStatusOnline:
2775 : case CRC_userStatusOffline:
2776 : case CRC_userStatusRecently:
2777 : case CRC_userStatusLastWeek:
2778 : case CRC_userStatusLastMonth:
2779 5 : if (r->len - r->pos < 4) return -1;
2780 5 : tl_read_int32(r);
2781 5 : return 0;
2782 1 : default:
2783 1 : logger_log(LOG_WARN, "tl_skip_user_status: unknown 0x%08x", crc);
2784 1 : return -1;
2785 : }
2786 : }
2787 :
2788 : /* ---- Vector<RestrictionReason> ----
2789 : * restrictionReason#d072acb4 platform:string reason:string text:string
2790 : */
2791 5 : int tl_skip_restriction_reason_vector(TlReader *r) {
2792 5 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2793 5 : uint32_t vec_crc = tl_read_uint32(r);
2794 5 : 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 5 : uint32_t count = tl_read_uint32(r);
2801 10 : for (uint32_t i = 0; i < count; i++) {
2802 6 : if (r->len - r->pos < 4) return -1;
2803 6 : uint32_t crc = tl_read_uint32(r);
2804 6 : if (crc != CRC_restrictionReason) {
2805 1 : logger_log(LOG_WARN,
2806 : "tl_skip_restriction_reason_vector: bad entry 0x%08x",
2807 : crc);
2808 1 : return -1;
2809 : }
2810 5 : if (tl_skip_string(r) != 0) return -1; /* platform */
2811 5 : if (tl_skip_string(r) != 0) return -1; /* reason */
2812 5 : if (tl_skip_string(r) != 0) return -1; /* text */
2813 : }
2814 4 : return 0;
2815 : }
2816 :
2817 : /* ---- Vector<Username> ----
2818 : * username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string
2819 : */
2820 2 : int tl_skip_username_vector(TlReader *r) {
2821 2 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2822 2 : uint32_t vec_crc = tl_read_uint32(r);
2823 2 : 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 2 : uint32_t count = tl_read_uint32(r);
2829 4 : for (uint32_t i = 0; i < count; i++) {
2830 3 : if (r->len - r->pos < 8) return -1;
2831 3 : uint32_t crc = tl_read_uint32(r);
2832 3 : if (crc != CRC_username) {
2833 1 : logger_log(LOG_WARN,
2834 : "tl_skip_username_vector: bad entry 0x%08x", crc);
2835 1 : return -1;
2836 : }
2837 2 : tl_read_uint32(r); /* flags — only 'true' bits, no data */
2838 2 : if (tl_skip_string(r) != 0) return -1;
2839 : }
2840 1 : return 0;
2841 : }
2842 :
2843 : /* ---- PeerColor ----
2844 : * peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long
2845 : */
2846 2 : int tl_skip_peer_color(TlReader *r) {
2847 2 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2848 2 : uint32_t crc = tl_read_uint32(r);
2849 2 : if (crc != CRC_peerColor) {
2850 1 : logger_log(LOG_WARN, "tl_skip_peer_color: unknown 0x%08x", crc);
2851 1 : return -1;
2852 : }
2853 1 : uint32_t flags = tl_read_uint32(r);
2854 1 : if (flags & (1u << 0)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
2855 1 : if (flags & (1u << 1)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
2856 1 : return 0;
2857 : }
2858 :
2859 : /* ---- EmojiStatus ---- */
2860 4 : int tl_skip_emoji_status(TlReader *r) {
2861 4 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2862 4 : uint32_t crc = tl_read_uint32(r);
2863 4 : switch (crc) {
2864 1 : case CRC_emojiStatusEmpty:
2865 1 : return 0;
2866 1 : case CRC_emojiStatus:
2867 1 : if (r->len - r->pos < 8) return -1;
2868 1 : tl_read_int64(r);
2869 1 : return 0;
2870 1 : case CRC_emojiStatusUntil:
2871 1 : if (r->len - r->pos < 12) return -1;
2872 1 : tl_read_int64(r); tl_read_int32(r);
2873 1 : return 0;
2874 1 : default:
2875 1 : logger_log(LOG_WARN, "tl_skip_emoji_status: unknown 0x%08x", crc);
2876 1 : return -1;
2877 : }
2878 : }
2879 :
2880 : /* ChatAdminRights#5fb224d5 flags:# — single uint32. */
2881 3 : static int skip_chat_admin_rights(TlReader *r) {
2882 3 : if (!tl_reader_ok(r) || r->len - r->pos < 8) return -1;
2883 3 : uint32_t crc = tl_read_uint32(r);
2884 3 : if (crc != CRC_chatAdminRights) {
2885 1 : logger_log(LOG_WARN, "skip_chat_admin_rights: unknown 0x%08x", crc);
2886 1 : return -1;
2887 : }
2888 2 : tl_read_uint32(r); /* flags */
2889 2 : return 0;
2890 : }
2891 :
2892 : /* ChatBannedRights#9f120418 flags:# until_date:int. */
2893 4 : static int skip_chat_banned_rights(TlReader *r) {
2894 4 : if (!tl_reader_ok(r) || r->len - r->pos < 12) return -1;
2895 4 : uint32_t crc = tl_read_uint32(r);
2896 4 : if (crc != CRC_chatBannedRights) {
2897 1 : logger_log(LOG_WARN, "skip_chat_banned_rights: unknown 0x%08x", crc);
2898 1 : return -1;
2899 : }
2900 3 : tl_read_uint32(r); /* flags */
2901 3 : tl_read_int32(r); /* until_date */
2902 3 : 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 8 : static int extract_chat_inner(TlReader *r, ChatSummary *out) {
2917 8 : if (out) {
2918 4 : out->id = 0;
2919 4 : out->access_hash = 0;
2920 4 : out->have_access_hash = 0;
2921 4 : out->title[0] = '\0';
2922 : }
2923 :
2924 8 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
2925 8 : uint32_t crc = tl_read_uint32(r);
2926 :
2927 8 : if (crc == TL_chatEmpty) {
2928 2 : if (r->len - r->pos < 8) return -1;
2929 2 : int64_t id = tl_read_int64(r);
2930 2 : if (out) out->id = id;
2931 2 : return 0;
2932 : }
2933 :
2934 6 : if (crc == TL_chatForbidden) {
2935 1 : if (r->len - r->pos < 8) return -1;
2936 1 : int64_t id = tl_read_int64(r);
2937 1 : if (out) out->id = id;
2938 1 : if (out) {
2939 1 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2940 0 : return -1;
2941 : } else {
2942 0 : if (tl_skip_string(r) != 0) return -1;
2943 : }
2944 1 : return 0;
2945 : }
2946 :
2947 5 : if (crc == TL_chat) {
2948 3 : if (r->len - r->pos < 4) return -1;
2949 3 : uint32_t flags = tl_read_uint32(r);
2950 : /* migrated_to:flags.6?InputChannel — too complex to dispatch here. */
2951 3 : 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 3 : if (r->len - r->pos < 8) return -1;
2958 3 : int64_t id = tl_read_int64(r);
2959 3 : if (out) out->id = id;
2960 3 : if (out) {
2961 1 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2962 0 : return -1;
2963 : } else {
2964 2 : if (tl_skip_string(r) != 0) return -1;
2965 : }
2966 3 : if (tl_skip_chat_photo(r) != 0) return -1;
2967 : /* participants_count:int date:int version:int */
2968 3 : if (r->len - r->pos < 12) return -1;
2969 3 : tl_read_int32(r); tl_read_int32(r); tl_read_int32(r);
2970 3 : if (flags & (1u << 14)) {
2971 2 : if (skip_chat_admin_rights(r) != 0) return -1;
2972 : }
2973 2 : if (flags & (1u << 18)) {
2974 2 : if (skip_chat_banned_rights(r) != 0) return -1;
2975 : }
2976 1 : return 0;
2977 : }
2978 :
2979 2 : if (crc == TL_channelForbidden) {
2980 0 : if (r->len - r->pos < 4) return -1;
2981 0 : uint32_t flags = tl_read_uint32(r);
2982 0 : if (r->len - r->pos < 16) return -1;
2983 0 : int64_t id = tl_read_int64(r);
2984 0 : if (out) out->id = id;
2985 0 : int64_t access_hash = tl_read_int64(r);
2986 0 : if (out) {
2987 0 : out->access_hash = access_hash;
2988 0 : out->have_access_hash = 1;
2989 : }
2990 0 : if (out) {
2991 0 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
2992 0 : return -1;
2993 : } else {
2994 0 : if (tl_skip_string(r) != 0) return -1;
2995 : }
2996 0 : if (flags & (1u << 16)) {
2997 0 : if (r->len - r->pos < 4) return -1;
2998 0 : tl_read_int32(r); /* until_date */
2999 : }
3000 0 : return 0;
3001 : }
3002 :
3003 2 : if (crc == TL_channel) {
3004 1 : if (r->len - r->pos < 8) return -1;
3005 1 : uint32_t flags = tl_read_uint32(r);
3006 1 : uint32_t flags2 = tl_read_uint32(r);
3007 : /* Known flags2 bits: 0,4,7,8,9,10,11. Reject any others. */
3008 1 : const uint32_t flags2_known =
3009 : (1u << 0) | (1u << 4) | (1u << 7) | (1u << 8) |
3010 : (1u << 9) | (1u << 10) | (1u << 11);
3011 1 : 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 1 : if (r->len - r->pos < 8) return -1;
3018 1 : int64_t id = tl_read_int64(r);
3019 1 : if (out) out->id = id;
3020 1 : if (flags & (1u << 13)) {
3021 0 : if (r->len - r->pos < 8) return -1;
3022 0 : int64_t access_hash = tl_read_int64(r);
3023 0 : if (out) {
3024 0 : out->access_hash = access_hash;
3025 0 : out->have_access_hash = 1;
3026 : }
3027 : }
3028 1 : if (out) {
3029 1 : if (read_string_into(r, out->title, sizeof(out->title)) != 0)
3030 0 : return -1;
3031 : } else {
3032 0 : if (tl_skip_string(r) != 0) return -1;
3033 : }
3034 1 : if (flags & (1u << 6)) {
3035 0 : if (tl_skip_string(r) != 0) return -1; /* username */
3036 : }
3037 1 : if (tl_skip_chat_photo(r) != 0) return -1;
3038 : /* date:int */
3039 1 : if (r->len - r->pos < 4) return -1;
3040 1 : tl_read_int32(r);
3041 1 : if (flags & (1u << 9)) {
3042 0 : if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3043 : }
3044 1 : if (flags & (1u << 14)) {
3045 1 : if (skip_chat_admin_rights(r) != 0) return -1;
3046 : }
3047 1 : if (flags & (1u << 15)) {
3048 1 : if (skip_chat_banned_rights(r) != 0) return -1;
3049 : }
3050 1 : if (flags & (1u << 18)) {
3051 1 : if (skip_chat_banned_rights(r) != 0) return -1;
3052 : }
3053 1 : if (flags & (1u << 17)) {
3054 0 : if (r->len - r->pos < 4) return -1;
3055 0 : tl_read_int32(r); /* participants_count */
3056 : }
3057 1 : if (flags2 & (1u << 0)) {
3058 0 : if (tl_skip_username_vector(r) != 0) return -1;
3059 : }
3060 1 : 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 1 : if (flags2 & (1u << 7)) {
3065 0 : if (tl_skip_peer_color(r) != 0) return -1;
3066 : }
3067 1 : if (flags2 & (1u << 8)) {
3068 0 : if (tl_skip_peer_color(r) != 0) return -1;
3069 : }
3070 1 : if (flags2 & (1u << 9)) {
3071 0 : if (tl_skip_emoji_status(r) != 0) return -1;
3072 : }
3073 1 : if (flags2 & (1u << 10)) {
3074 0 : if (r->len - r->pos < 4) return -1;
3075 0 : tl_read_int32(r); /* level */
3076 : }
3077 1 : 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 1 : return 0;
3082 : }
3083 :
3084 1 : logger_log(LOG_WARN, "tl_skip_chat: unknown Chat variant 0x%08x", crc);
3085 1 : return -1;
3086 : }
3087 :
3088 2 : int tl_skip_chat(TlReader *r) {
3089 2 : return extract_chat_inner(r, NULL);
3090 : }
3091 :
3092 6 : int tl_extract_chat(TlReader *r, ChatSummary *out) {
3093 6 : if (!out) return extract_chat_inner(r, NULL);
3094 4 : 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 7 : static int extract_user_inner(TlReader *r, UserSummary *out) {
3108 7 : if (out) {
3109 6 : out->id = 0;
3110 6 : out->access_hash = 0;
3111 6 : out->have_access_hash = 0;
3112 6 : out->name[0] = '\0';
3113 6 : out->username[0] = '\0';
3114 : }
3115 :
3116 7 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
3117 7 : uint32_t crc = tl_read_uint32(r);
3118 :
3119 7 : if (crc == TL_userEmpty) {
3120 1 : if (r->len - r->pos < 8) return -1;
3121 1 : int64_t id = tl_read_int64(r);
3122 1 : if (out) out->id = id;
3123 1 : return 0;
3124 : }
3125 :
3126 6 : if (crc != TL_user) {
3127 1 : logger_log(LOG_WARN, "tl_skip_user: unknown User variant 0x%08x", crc);
3128 1 : return -1;
3129 : }
3130 :
3131 5 : if (r->len - r->pos < 8) return -1;
3132 5 : uint32_t flags = tl_read_uint32(r);
3133 5 : uint32_t flags2 = tl_read_uint32(r);
3134 : /* Known flags2 bits go up to 13. Reject any bits above that. */
3135 5 : 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 5 : if (r->len - r->pos < 8) return -1;
3141 5 : int64_t id = tl_read_int64(r);
3142 5 : if (out) out->id = id;
3143 :
3144 5 : if (flags & (1u << 0)) {
3145 5 : if (r->len - r->pos < 8) return -1;
3146 5 : int64_t access_hash = tl_read_int64(r);
3147 5 : if (out) {
3148 5 : out->access_hash = access_hash;
3149 5 : out->have_access_hash = 1;
3150 : }
3151 : }
3152 : /* first_name */
3153 5 : if (flags & (1u << 1)) {
3154 4 : if (out) {
3155 4 : char first[96] = {0};
3156 4 : if (read_string_into(r, first, sizeof(first)) != 0) return -1;
3157 4 : str_append(out->name, sizeof(out->name), first);
3158 : } else {
3159 0 : if (tl_skip_string(r) != 0) return -1;
3160 : }
3161 : }
3162 : /* last_name */
3163 5 : if (flags & (1u << 2)) {
3164 1 : if (out) {
3165 1 : char last[96] = {0};
3166 1 : if (read_string_into(r, last, sizeof(last)) != 0) return -1;
3167 1 : if (last[0] != '\0') {
3168 1 : if (out->name[0] != '\0') {
3169 1 : str_append(out->name, sizeof(out->name), " ");
3170 : }
3171 1 : str_append(out->name, sizeof(out->name), last);
3172 : }
3173 : } else {
3174 0 : if (tl_skip_string(r) != 0) return -1;
3175 : }
3176 : }
3177 : /* username */
3178 5 : if (flags & (1u << 3)) {
3179 1 : if (out) {
3180 1 : if (read_string_into(r, out->username, sizeof(out->username)) != 0)
3181 0 : return -1;
3182 : } else {
3183 0 : if (tl_skip_string(r) != 0) return -1;
3184 : }
3185 : }
3186 : /* phone */
3187 5 : if (flags & (1u << 4)) {
3188 1 : if (tl_skip_string(r) != 0) return -1;
3189 : }
3190 : /* photo */
3191 5 : if (flags & (1u << 5)) {
3192 0 : if (tl_skip_user_profile_photo(r) != 0) return -1;
3193 : }
3194 : /* status */
3195 5 : if (flags & (1u << 6)) {
3196 0 : if (tl_skip_user_status(r) != 0) return -1;
3197 : }
3198 : /* bot_info_version */
3199 5 : 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 5 : if (flags & (1u << 18)) {
3205 0 : if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3206 : }
3207 : /* bot_inline_placeholder */
3208 5 : if (flags & (1u << 19)) {
3209 0 : if (tl_skip_string(r) != 0) return -1;
3210 : }
3211 : /* lang_code */
3212 5 : if (flags & (1u << 22)) {
3213 0 : if (tl_skip_string(r) != 0) return -1;
3214 : }
3215 : /* emoji_status */
3216 5 : if (flags & (1u << 30)) {
3217 0 : if (tl_skip_emoji_status(r) != 0) return -1;
3218 : }
3219 : /* usernames */
3220 5 : if (flags2 & (1u << 0)) {
3221 0 : if (tl_skip_username_vector(r) != 0) return -1;
3222 : }
3223 : /* stories_max_id */
3224 5 : if (flags2 & (1u << 5)) {
3225 0 : if (r->len - r->pos < 4) return -1;
3226 0 : tl_read_int32(r);
3227 : }
3228 : /* color */
3229 5 : if (flags2 & (1u << 8)) {
3230 0 : if (tl_skip_peer_color(r) != 0) return -1;
3231 : }
3232 : /* profile_color */
3233 5 : if (flags2 & (1u << 9)) {
3234 0 : if (tl_skip_peer_color(r) != 0) return -1;
3235 : }
3236 : /* bot_active_users */
3237 5 : if (flags2 & (1u << 12)) {
3238 0 : if (r->len - r->pos < 4) return -1;
3239 0 : tl_read_int32(r);
3240 : }
3241 5 : return 0;
3242 : }
3243 :
3244 1 : int tl_skip_user(TlReader *r) {
3245 1 : return extract_user_inner(r, NULL);
3246 : }
3247 :
3248 6 : int tl_extract_user(TlReader *r, UserSummary *out) {
3249 6 : if (!out) return extract_user_inner(r, NULL);
3250 6 : 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 14 : int tl_skip_message(TlReader *r) {
3259 14 : if (!tl_reader_ok(r) || r->len - r->pos < 4) return -1;
3260 13 : uint32_t crc = tl_read_uint32(r);
3261 :
3262 13 : if (crc == TL_messageEmpty) {
3263 : /* flags:# id:int peer_id:flags.0?Peer */
3264 2 : if (r->len - r->pos < 8) return -1;
3265 2 : uint32_t flags = tl_read_uint32(r);
3266 2 : tl_read_int32(r); /* id */
3267 2 : if (flags & 1u) {
3268 0 : if (tl_skip_peer(r) != 0) return -1;
3269 : }
3270 2 : return 0;
3271 : }
3272 :
3273 11 : if (crc == TL_messageService) {
3274 : /* action-heavy; we do not implement skipping yet. */
3275 1 : return -1;
3276 : }
3277 :
3278 10 : if (crc != TL_message) {
3279 1 : logger_log(LOG_WARN, "tl_skip_message: unknown 0x%08x", crc);
3280 1 : return -1;
3281 : }
3282 :
3283 9 : if (r->len - r->pos < 12) return -1;
3284 8 : uint32_t flags = tl_read_uint32(r);
3285 8 : uint32_t flags2 = tl_read_uint32(r);
3286 8 : tl_read_int32(r); /* id */
3287 :
3288 8 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) return -1; /* from_id */
3289 8 : if (tl_skip_peer(r) != 0) return -1; /* peer_id */
3290 8 : if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) return -1; /* saved_peer_id */
3291 8 : if (flags & (1u << 2)) if (tl_skip_message_fwd_header(r) != 0) return -1;
3292 8 : if (flags & (1u << 11)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3293 8 : if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3294 8 : if (flags & (1u << 3)) if (tl_skip_message_reply_header(r) != 0) return -1;
3295 :
3296 8 : if (r->len - r->pos < 4) return -1;
3297 8 : tl_read_int32(r); /* date */
3298 8 : if (tl_skip_string(r) != 0) return -1; /* message */
3299 :
3300 8 : if (flags & (1u << 9)) if (tl_skip_message_media(r) != 0) return -1;
3301 :
3302 7 : if (flags & (1u << 6)) if (tl_skip_reply_markup(r) != 0) return -1;
3303 6 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) return -1;
3304 6 : if (flags & (1u << 10)) { if (r->len - r->pos < 8) return -1; tl_read_int32(r); tl_read_int32(r); }
3305 6 : if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) return -1;
3306 6 : if (flags & (1u << 15)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3307 6 : if (flags & (1u << 16)) if (tl_skip_string(r) != 0) return -1;
3308 6 : if (flags & (1u << 17)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3309 6 : if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) return -1;
3310 6 : if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) return -1;
3311 6 : if (flags & (1u << 25)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3312 6 : if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) return -1; tl_read_int32(r); }
3313 6 : if (flags2 & (1u << 2)) { if (r->len - r->pos < 8) return -1; tl_read_int64(r); }
3314 6 : if (flags2 & (1u << 3)) if (tl_skip_factcheck(r) != 0) return -1;
3315 6 : return 0;
3316 : }
|