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 6 : static int write_input_peer(TlWriter *w, const HistoryPeer *p) {
28 6 : switch (p->kind) {
29 3 : case HISTORY_PEER_SELF:
30 3 : tl_write_uint32(w, TL_inputPeerSelf); return 0;
31 3 : case HISTORY_PEER_USER:
32 3 : tl_write_uint32(w, TL_inputPeerUser);
33 3 : tl_write_int64 (w, p->peer_id);
34 3 : tl_write_int64 (w, p->access_hash);
35 3 : 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 119 : static int parse_message(TlReader *r, HistoryEntry *out) {
53 119 : if (!tl_reader_ok(r)) return -1;
54 119 : uint32_t crc = tl_read_uint32(r);
55 119 : if (crc == TL_messageEmpty) {
56 3 : uint32_t flags = tl_read_uint32(r);
57 3 : out->id = tl_read_int32(r);
58 3 : if (flags & 1u) { tl_read_uint32(r); tl_read_int64(r); }
59 3 : return 0;
60 : }
61 116 : if (crc != TL_message && crc != TL_messageService) return -1;
62 :
63 116 : uint32_t flags = tl_read_uint32(r);
64 116 : uint32_t flags2 = 0;
65 116 : if (crc == TL_message) flags2 = tl_read_uint32(r);
66 116 : out->out = (flags & (1u << 1)) ? 1 : 0;
67 116 : out->id = tl_read_int32(r);
68 116 : if (crc == TL_messageService) {
69 0 : return domain_history_parse_service(r, out, flags);
70 : }
71 :
72 116 : if (flags & (1u << 8)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
73 116 : if (tl_skip_peer(r) != 0) { out->complex = 1; return -1; }
74 116 : if (flags & (1u << 28)) if (tl_skip_peer(r) != 0) { out->complex=1; return -1; }
75 116 : if (flags & (1u << 2)) if (tl_skip_message_fwd_header(r) != 0) { out->complex=1; return -1; }
76 116 : if (flags & (1u << 11)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
77 116 : if (flags2 & (1u << 0)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
78 116 : if (flags & (1u << 3)) if (tl_skip_message_reply_header(r) != 0) { out->complex=1; return -1; }
79 116 : if (r->len - r->pos < 4) { out->complex=1; return -1; }
80 116 : out->date = tl_read_int32(r);
81 :
82 116 : char *msg = tl_read_string(r);
83 116 : if (msg) {
84 116 : size_t n = strlen(msg);
85 116 : if (n >= HISTORY_TEXT_MAX) { n = HISTORY_TEXT_MAX - 1; out->truncated = 1; }
86 116 : memcpy(out->text, msg, n);
87 116 : out->text[n] = '\0';
88 116 : free(msg);
89 : }
90 :
91 116 : 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 116 : if (flags & (1u << 6)) if (tl_skip_reply_markup(r) != 0) { out->complex=1; return -1; }
104 116 : if (flags & (1u << 7)) if (tl_skip_message_entities_vector(r) != 0) { out->complex=1; return -1; }
105 116 : if (flags & (1u << 10)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int32(r); tl_read_int32(r); }
106 116 : if (flags & (1u << 23)) if (tl_skip_message_replies(r) != 0) { out->complex=1; return -1; }
107 116 : if (flags & (1u << 15)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
108 116 : if (flags & (1u << 16)) if (tl_skip_string(r) != 0) { out->complex=1; return -1; }
109 116 : if (flags & (1u << 17)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
110 116 : if (flags & (1u << 20)) if (tl_skip_message_reactions(r) != 0) { out->complex=1; return -1; }
111 116 : if (flags & (1u << 22)) if (tl_skip_restriction_reason_vector(r) != 0) { out->complex=1; return -1; }
112 116 : if (flags & (1u << 25)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
113 116 : if (flags2 & (1u << 30)) { if (r->len - r->pos < 4) { out->complex=1; return -1; } tl_read_int32(r); }
114 116 : if (flags2 & (1u << 2)) { if (r->len - r->pos < 8) { out->complex=1; return -1; } tl_read_int64(r); }
115 116 : if (flags2 & (1u << 3)) if (tl_skip_factcheck(r) != 0) { out->complex=1; return -1; }
116 116 : return 0;
117 : }
118 :
119 12 : static int parse_top(const uint8_t *resp, size_t resp_len,
120 : HistoryEntry *out, int limit, int *out_count) {
121 12 : TlReader r = tl_reader_init(resp, resp_len);
122 12 : uint32_t top = tl_read_uint32(&r);
123 :
124 12 : if (top == TL_rpc_error) {
125 1 : RpcError err; rpc_parse_error(resp, resp_len, &err);
126 1 : logger_log(LOG_ERROR, "search: RPC error %d: %s",
127 : err.error_code, err.error_msg);
128 1 : return -1;
129 : }
130 :
131 11 : 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 11 : if (top == TL_messages_messagesSlice) {
139 2 : tl_read_uint32(&r); tl_read_int32(&r); /* flags, count */
140 9 : } else if (top == TL_messages_channelMessages) {
141 0 : tl_read_uint32(&r); tl_read_int32(&r); tl_read_int32(&r);
142 : }
143 :
144 11 : uint32_t vec = tl_read_uint32(&r);
145 11 : if (vec != TL_vector) return -1;
146 11 : uint32_t count = tl_read_uint32(&r);
147 11 : int written = 0;
148 130 : for (uint32_t i = 0; i < count && written < limit; i++) {
149 119 : HistoryEntry e = {0};
150 119 : int rc = parse_message(&r, &e);
151 119 : if (e.id != 0 || e.text[0] != '\0') out[written++] = e;
152 119 : if (rc != 0) break;
153 : }
154 11 : *out_count = written;
155 11 : return 0;
156 : }
157 :
158 7 : 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 7 : if (!cfg || !s || !t || !peer || !query || !out || !out_count || limit <= 0)
164 1 : return -1;
165 6 : *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 6 : TlWriter w; tl_writer_init(&w);
175 6 : tl_write_uint32(&w, CRC_messages_search);
176 6 : tl_write_uint32(&w, 0); /* flags = 0 */
177 6 : if (write_input_peer(&w, peer) != 0) { tl_writer_free(&w); return -1; }
178 6 : tl_write_string(&w, query);
179 6 : tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
180 6 : tl_write_int32 (&w, 0); /* min_date */
181 6 : tl_write_int32 (&w, 0); /* max_date */
182 6 : tl_write_int32 (&w, 0); /* offset_id */
183 6 : tl_write_int32 (&w, 0); /* add_offset */
184 6 : tl_write_int32 (&w, limit);
185 6 : tl_write_int32 (&w, 0); /* max_id */
186 6 : tl_write_int32 (&w, 0); /* min_id */
187 6 : tl_write_int64 (&w, 0); /* hash */
188 :
189 : uint8_t query_buf[2048];
190 6 : if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
191 6 : memcpy(query_buf, w.data, w.len);
192 6 : size_t qlen = w.len;
193 6 : tl_writer_free(&w);
194 :
195 6 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
196 6 : if (!resp) return -1;
197 6 : size_t resp_len = 0;
198 6 : if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
199 0 : return -1;
200 6 : if (resp_len < 4) return -1;
201 6 : return parse_top(resp, resp_len, out, limit, out_count);
202 : }
203 :
204 7 : 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 7 : if (!cfg || !s || !t || !query || !out || !out_count || limit <= 0) return -1;
209 6 : *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 6 : TlWriter w; tl_writer_init(&w);
217 6 : tl_write_uint32(&w, CRC_messages_searchGlobal);
218 6 : tl_write_uint32(&w, 0); /* flags */
219 6 : tl_write_string(&w, query);
220 6 : tl_write_uint32(&w, CRC_inputMessagesFilterEmpty);
221 6 : tl_write_int32 (&w, 0); /* min_date */
222 6 : tl_write_int32 (&w, 0); /* max_date */
223 6 : tl_write_int32 (&w, 0); /* offset_rate */
224 6 : tl_write_uint32(&w, TL_inputPeerEmpty); /* offset_peer */
225 6 : tl_write_int32 (&w, 0); /* offset_id */
226 6 : tl_write_int32 (&w, limit);
227 :
228 : uint8_t query_buf[2048];
229 6 : if (w.len > sizeof(query_buf)) { tl_writer_free(&w); return -1; }
230 6 : memcpy(query_buf, w.data, w.len);
231 6 : size_t qlen = w.len;
232 6 : tl_writer_free(&w);
233 :
234 6 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(262144);
235 6 : if (!resp) return -1;
236 6 : size_t resp_len = 0;
237 6 : if (api_call(cfg, s, t, query_buf, qlen, resp, 262144, &resp_len) != 0)
238 0 : return -1;
239 6 : if (resp_len < 4) return -1;
240 6 : return parse_top(resp, resp_len, out, limit, out_count);
241 : }
|