Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file domain/read/search.c
6 : * @brief messages.search / messages.searchGlobal minimal parser.
7 : */
8 :
9 : #include "domain/read/search.h"
10 :
11 : #include "tl_serial.h"
12 : #include "tl_registry.h"
13 : #include "tl_skip.h"
14 : #include "mtproto_rpc.h"
15 : #include "logger.h"
16 : #include "raii.h"
17 :
18 : #include <stdlib.h>
19 : #include <string.h>
20 :
21 : #define CRC_messages_search 0x29ee847au
22 : #define CRC_messages_searchGlobal 0x4bc6589au
23 : #define CRC_inputMessagesFilterEmpty 0x57e9a944u
24 :
25 : /* Input-peer writer shared with history.c would live under app/, but to
26 : * keep the library split tidy we duplicate the small switch here. */
27 2 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
28 2 : switch (p->kind) {
29 1 : case HISTORY_PEER_SELF:
30 1 : tl_write_uint32(w, TL_inputPeerSelf); return 0;
31 1 : case HISTORY_PEER_USER:
32 1 : tl_write_uint32(w, TL_inputPeerUser);
33 1 : tl_write_int64 (w, p->peer_id);
34 1 : tl_write_int64 (w, p->access_hash);
35 1 : return 0;
36 0 : case HISTORY_PEER_CHAT:
37 0 : tl_write_uint32(w, TL_inputPeerChat);
38 0 : tl_write_int64 (w, p->peer_id);
39 0 : return 0;
40 0 : case HISTORY_PEER_CHANNEL:
41 0 : tl_write_uint32(w, TL_inputPeerChannel);
42 0 : tl_write_int64 (w, p->peer_id);
43 0 : tl_write_int64 (w, p->access_hash);
44 0 : return 0;
45 0 : default: return -1;
46 : }
47 : }
48 :
49 : /* All trailer flags now have skippers; factcheck (flags2.3) still halts. */
50 :
51 : /* Message parser — identical semantics to history.c::parse_message. */
52 58 : static int parse_message(TlReader *r, HistoryEntry *out) {
53 58 : if (!tl_reader_ok(r)) return -1;
54 58 : uint32_t crc = tl_read_uint32(r);
55 58 : if (crc == TL_messageEmpty) {
56 0 : uint32_t flags = tl_read_uint32(r);
57 0 : out->id = tl_read_int32(r);
58 0 : if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
59 0 : return 0;
60 : }
61 58 : if (crc != TL_message && crc != TL_messageService) return -1;
62 :
63 58 : uint32_t flags = tl_read_uint32(r);
64 58 : uint32_t flags2 = 0;
65 58 : if (crc == TL_message) flags2 = tl_read_uint32(r);
66 58 : out->out = (flags & (1u << 1)) ? 1 : 0;
67 58 : out->id = tl_read_int32(r);
68 58 : if (crc == TL_messageService) {
69 0 : return domain_history_parse_service(r, out, flags);
70 : }
71 :
72 58 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
73 58 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
74 58 : if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
75 58 : if (flags & (1u << 2)) if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
76 58 : if (flags & (1u << 11)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
77 58 : if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
78 58 : if (flags & (1u << 3)) if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
79 58 : if (r->len - r->pos < 4) { out->complex=1; return -1; }
80 58 : out->date = tl_read_int32(r);
81 :
82 58 : char *msg = tl_read_string(r);
83 58 : if (msg) {
84 58 : size_t n = strlen(msg);
85 58 : if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
86 58 : memcpy(out->text, msg, n);
87 58 : out->text[n] = '\0';
88 58 : free(msg);
89 : }
90 :
91 58 : if (flags & (1u << 9)) {
92 0 : MediaInfo mi = {0};
93 0 : if (tl_skip_message_media_ex(r, &mi) != 0) {
94 0 : out->media = mi.kind;
95 0 : out->complex = 1;
96 0 : return -1;
97 : }
98 0 : out->media = mi.kind;
99 0 : out->media_id = (mi.kind == MEDIA_PHOTO) ? mi.photo_id
100 0 : : (mi.kind == MEDIA_DOCUMENT) ? mi.document_id : 0;
101 0 : out->media_dc = mi.dc_id;
102 : }
103 58 : if (flags & (1u << 6)) if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
104 58 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
105 58 : if (flags & (1u << 10)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
106 58 : if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
107 58 : if (flags & (1u << 15)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
108 58 : if (flags & (1u << 16)) if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
109 58 : if (flags & (1u << 17)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
110 58 : if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
111 58 : if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
112 58 : if (flags & (1u << 25)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
113 58 : if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
114 58 : if (flags2 & (1u << 2)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
115 58 : if (flags2 & (1u << 3)) if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
116 58 : return 0;
117 : }
118 :
119 4 : static int parse_top(const uint8_t *resp, size_t resp_len,
120 : HistoryEntry *out, int limit, int *out_count) {
121 4 : TlReader r = tl_reader_init(resp, resp_len);
122 4 : uint32_t top = tl_read_uint32(&r);
123 :
124 4 : if (top == TL_rpc_error) {
125 0 : RpcError err; rpc_parse_error(resp, resp_len, &err);
126 0 : logger_log(LOG_ERROR, "search: RPC error %d: %s",
127 : err.error_code, err.error_msg);
128 0 : return -1;
129 : }
130 :
131 4 : if (top != TL_messages_messages &&
132 0 : top != TL_messages_messagesSlice &&
133 : top != TL_messages_channelMessages) {
134 0 : logger_log(LOG_ERROR, "search: unexpected top 0x%08x", top);
135 0 : return -1;
136 : }
137 :
138 4 : if (top == TL_messages_messagesSlice) {
139 1 : tl_read_uint32(&r); tl_read_int32(&r); /* flags, count */
140 3 : } else if (top == TL_messages_channelMessages) {
141 0 : tl_read_uint32(&r); tl_read_int32(&r); tl_read_int32(&r);
142 : }
143 :
144 4 : uint32_t vec = tl_read_uint32(&r);
145 4 : if (vec != TL_vector) return -1;
146 4 : uint32_t count = tl_read_uint32(&r);
147 4 : int written = 0;
148 62 : for (uint32_t i = 0; i < count && written < limit; i++) {
149 58 : HistoryEntry e = {0};
150 58 : int rc = parse_message(&r, &e);
151 58 : if (e.id != 0 || e.text[0] != '\0') out[written++] = e;
152 58 : if (rc != 0) break;
153 : }
154 4 : *out_count = written;
155 4 : return 0;
156 : }
157 :
158 2 : int domain_search_peer(const ApiConfig *cfg,
159 : MtProtoSession *s, Transport *t,
160 : const HistoryPeer *peer, const char *query,
161 : int limit,
162 : HistoryEntry *out, int *out_count) {
163 2 : if (!cfg || !s || !t || !peer || !query || !out || !out_count || limit <= 0)
164 0 : return -1;
165 2 : *out_count = 0;
166 :
167 : /* messages.search#29ee847a flags:# peer:InputPeer q:string
168 : * from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer
169 : * saved_reaction:flags.3?Vector<Reaction>
170 : * top_msg_id:flags.1?int filter:MessagesFilter
171 : * min_date:int max_date:int offset_id:int add_offset:int
172 : * limit:int max_id:int min_id:int hash:long = messages.Messages
173 : */
174 2 : TlWriter w; tl_writer_init(&w);
175 2 : tl_write_uint32(&w, CRC_messages_search);
176 2 : tl_write_uint32(&w, 0); /* flags = 0 */
177 2 : if (write_input_peer(&w, peer) != 0) { tl_writer_free(&w); return -1; }
178 2 : tl_write_string(&w, query);
179 2 : tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
180 2 : tl_write_int32 (&w, 0); /* min_date */
181 2 : tl_write_int32 (&w, 0); /* max_date */
182 2 : tl_write_int32 (&w, 0); /* offset_id */
183 2 : tl_write_int32 (&w, 0); /* add_offset */
184 2 : tl_write_int32 (&w, limit);
185 2 : tl_write_int32 (&w, 0); /* max_id */
186 2 : tl_write_int32 (&w, 0); /* min_id */
187 2 : tl_write_int64 (&w, 0); /* hash */
188 :
189 : uint8_t query_buf[2048];
190 2 : if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
191 2 : memcpy(query_buf, w.data, w.len);
192 2 : size_t qlen = w.len;
193 2 : tl_writer_free(&w);
194 :
195 2 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
196 2 : if (!resp) return -1;
197 2 : size_t resp_len = 0;
198 2 : if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
199 0 : return -1;
200 2 : if (resp_len < 4) return -1;
201 2 : return parse_top(resp, resp_len, out, limit, out_count);
202 : }
203 :
204 2 : int domain_search_global(const ApiConfig *cfg,
205 : MtProtoSession *s, Transport *t,
206 : const char *query, int limit,
207 : HistoryEntry *out, int *out_count) {
208 2 : if (!cfg || !s || !t || !query || !out || !out_count || limit <= 0) return -1;
209 2 : *out_count = 0;
210 :
211 : /* messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int
212 : * q:string filter:MessagesFilter min_date:int max_date:int
213 : * offset_rate:int offset_peer:InputPeer offset_id:int
214 : * limit:int = messages.Messages
215 : */
216 2 : TlWriter w; tl_writer_init(&w);
217 2 : tl_write_uint32(&w, CRC_messages_searchGlobal);
218 2 : tl_write_uint32(&w, 0); /* flags */
219 2 : tl_write_string(&w, query);
220 2 : tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
221 2 : tl_write_int32 (&w, 0); /* min_date */
222 2 : tl_write_int32 (&w, 0); /* max_date */
223 2 : tl_write_int32 (&w, 0); /* offset_rate */
224 2 : tl_write_uint32(&w, TL_inputPeerEmpty); /* offset_peer */
225 2 : tl_write_int32 (&w, 0); /* offset_id */
226 2 : tl_write_int32 (&w, limit);
227 :
228 : uint8_t query_buf[2048];
229 2 : if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
230 2 : memcpy(query_buf, w.data, w.len);
231 2 : size_t qlen = w.len;
232 2 : tl_writer_free(&w);
233 :
234 2 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
235 2 : if (!resp) return -1;
236 2 : size_t resp_len = 0;
237 2 : if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
238 0 : return -1;
239 2 : if (resp_len < 4) return -1;
240 2 : return parse_top(resp, resp_len, out, limit, out_count);
241 : }
|