/src/serenity/Userland/Libraries/LibIMAP/Parser.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me> |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <AK/CharacterTypes.h> |
8 | | #include <AK/Debug.h> |
9 | | #include <LibIMAP/Parser.h> |
10 | | |
11 | | namespace IMAP { |
12 | | |
13 | | ParseStatus Parser::parse(ByteBuffer&& buffer, bool expecting_tag) |
14 | 13.3k | { |
15 | 13.3k | auto response_or_error = try_parse(move(buffer), expecting_tag); |
16 | 13.3k | if (response_or_error.is_error()) |
17 | 9.21k | return { false, {} }; |
18 | | |
19 | 4.13k | auto response = response_or_error.release_value(); |
20 | 4.13k | return response; |
21 | 13.3k | } |
22 | | |
23 | | ErrorOr<ParseStatus> Parser::try_parse(ByteBuffer&& buffer, bool expecting_tag) |
24 | 13.3k | { |
25 | 13.3k | dbgln_if(IMAP_PARSER_DEBUG, "Parser received {} bytes:\n\"{}\"", buffer.size(), StringView(buffer.data(), buffer.size())); |
26 | 13.3k | if (m_incomplete) { |
27 | 1.81k | m_buffer += buffer; |
28 | 1.81k | m_incomplete = false; |
29 | 11.5k | } else { |
30 | 11.5k | m_buffer = move(buffer); |
31 | 11.5k | m_position = 0; |
32 | 11.5k | m_response = SolidResponse(); |
33 | 11.5k | } |
34 | | |
35 | 13.3k | if (consume_if("+"sv)) { |
36 | 12 | TRY(consume(" "sv)); |
37 | 8 | auto data = consume_until_end_of_line(); |
38 | 8 | TRY(consume(" "sv)); |
39 | 0 | return ParseStatus { true, { ContinueRequest { data } } }; |
40 | 8 | } |
41 | | |
42 | 2.29M | while (consume_if("*"sv)) { |
43 | 2.29M | TRY(parse_untagged()); |
44 | 2.28M | } |
45 | | |
46 | 13.3k | if (expecting_tag) { |
47 | 2.27k | if (at_end()) { |
48 | 1.81k | m_incomplete = true; |
49 | 1.81k | return ParseStatus { true, {} }; |
50 | 1.81k | } |
51 | 2.27k | TRY(parse_response_done()); |
52 | 503 | } |
53 | | |
54 | 2.32k | return ParseStatus { true, { move(m_response) } }; |
55 | 4.55k | } |
56 | | |
57 | | bool Parser::consume_if(StringView x) |
58 | 435M | { |
59 | 435M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, consume({})", m_position, x); |
60 | 435M | size_t i = 0; |
61 | 435M | auto previous_position = m_position; |
62 | 663M | while (i < x.length() && !at_end() && to_ascii_lowercase(x[i]) == to_ascii_lowercase(m_buffer[m_position])) { |
63 | 227M | i++; |
64 | 227M | m_position++; |
65 | 227M | } |
66 | 435M | if (i != x.length()) { |
67 | | // We didn't match the full string. |
68 | 221M | m_position = previous_position; |
69 | 221M | dbgln_if(IMAP_PARSER_DEBUG, "ret false"); |
70 | 221M | return false; |
71 | 221M | } |
72 | | |
73 | 213M | dbgln_if(IMAP_PARSER_DEBUG, "ret true"); |
74 | 213M | return true; |
75 | 435M | } |
76 | | |
77 | | ErrorOr<void> Parser::parse_response_done() |
78 | 461 | { |
79 | 461 | TRY(consume("A"sv)); |
80 | 324 | auto tag = TRY(parse_number()); |
81 | 277 | TRY(consume(" "sv)); |
82 | | |
83 | 259 | ResponseStatus status = TRY(parse_status()); |
84 | 148 | TRY(consume(" "sv)); |
85 | | |
86 | 145 | m_response.m_tag = tag; |
87 | 145 | m_response.m_status = status; |
88 | | |
89 | 145 | StringBuilder response_data; |
90 | | |
91 | 7.78M | while (!at_end() && m_buffer[m_position] != '\r') { |
92 | 7.78M | response_data.append((char)m_buffer[m_position]); |
93 | 7.78M | m_position += 1; |
94 | 7.78M | } |
95 | | |
96 | 145 | TRY(consume("\r\n"sv)); |
97 | 42 | m_response.m_response_text = response_data.to_byte_string(); |
98 | 42 | return {}; |
99 | 145 | } |
100 | | |
101 | | ErrorOr<void> Parser::consume(StringView x) |
102 | 202M | { |
103 | 202M | if (!consume_if(x)) { |
104 | 7.24k | dbgln("\"{}\" not matched at {}, (buffer length {})", x, m_position, m_buffer.size()); |
105 | 7.24k | return Error::from_string_literal("Token not matched"); |
106 | 7.24k | } |
107 | | |
108 | 202M | return {}; |
109 | 202M | } |
110 | | |
111 | | Optional<unsigned> Parser::try_parse_number() |
112 | 2.43M | { |
113 | 2.43M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, try_parse_number()", m_position); |
114 | 2.43M | auto number_matched = 0; |
115 | 3.37M | while (!at_end() && 0 <= m_buffer[m_position] - '0' && m_buffer[m_position] - '0' <= 9) { |
116 | 942k | number_matched++; |
117 | 942k | m_position++; |
118 | 942k | } |
119 | 2.43M | if (number_matched == 0) { |
120 | 1.87M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret empty", m_position); |
121 | 1.87M | return {}; |
122 | 1.87M | } |
123 | | |
124 | 563k | auto number = StringView(m_buffer.data() + m_position - number_matched, number_matched); |
125 | | |
126 | 563k | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret \"{}\"", m_position, number.to_number<unsigned>()); |
127 | 563k | return number.to_number<unsigned>(); |
128 | 2.43M | } |
129 | | |
130 | | ErrorOr<unsigned> Parser::parse_number() |
131 | 144k | { |
132 | 144k | auto number = try_parse_number(); |
133 | 144k | if (!number.has_value()) { |
134 | 909 | dbgln("Failed to parse number at {}, (buffer length {})", m_position, m_buffer.size()); |
135 | 909 | return Error::from_string_view("Failed to parse expected number"sv); |
136 | 909 | } |
137 | | |
138 | 143k | return number.value(); |
139 | 144k | } |
140 | | |
141 | | ErrorOr<void> Parser::parse_untagged() |
142 | 2.29M | { |
143 | 2.29M | TRY(consume(" "sv)); |
144 | | |
145 | | // Certain messages begin with a number like: |
146 | | // * 15 EXISTS |
147 | 2.29M | auto number = try_parse_number(); |
148 | 2.29M | if (number.has_value()) { |
149 | 407k | TRY(consume(" "sv)); |
150 | 407k | auto data_type = TRY(parse_atom()); |
151 | 407k | if (data_type == "EXISTS"sv) { |
152 | 216 | m_response.data().set_exists(number.value()); |
153 | 216 | TRY(consume("\r\n"sv)); |
154 | 407k | } else if (data_type == "RECENT"sv) { |
155 | 200 | m_response.data().set_recent(number.value()); |
156 | 200 | TRY(consume("\r\n"sv)); |
157 | 407k | } else if (data_type == "FETCH"sv) { |
158 | 204k | auto fetch_response = TRY(parse_fetch_response()); |
159 | 199k | m_response.data().add_fetch_response(number.value(), move(fetch_response)); |
160 | 203k | } else if (data_type == "EXPUNGE"sv) { |
161 | 167k | m_response.data().add_expunged(number.value()); |
162 | 167k | TRY(consume("\r\n"sv)); |
163 | 167k | } |
164 | 407k | return {}; |
165 | 407k | } |
166 | | |
167 | 1.88M | if (consume_if("CAPABILITY"sv)) { |
168 | 536k | TRY(parse_capability_response()); |
169 | 1.34M | } else if (consume_if("LIST"sv)) { |
170 | 102k | auto item = TRY(parse_list_item()); |
171 | 102k | m_response.data().add_list_item(move(item)); |
172 | 1.24M | } else if (consume_if("LSUB"sv)) { |
173 | 69.9k | auto item = TRY(parse_list_item()); |
174 | 69.2k | m_response.data().add_lsub_item(move(item)); |
175 | 1.17M | } else if (consume_if("FLAGS"sv)) { |
176 | 13.6k | TRY(consume(" "sv)); |
177 | 13.6k | auto flags = TRY(parse_list(+[](StringView x) { return ByteString(x); })); |
178 | 13.4k | m_response.data().set_flags(move(flags)); |
179 | 13.4k | TRY(consume("\r\n"sv)); |
180 | 1.16M | } else if (consume_if("OK"sv)) { |
181 | 28.2k | TRY(consume(" "sv)); |
182 | 28.2k | if (consume_if("["sv)) { |
183 | 22.8k | auto actual_type = TRY(parse_atom()); |
184 | 22.8k | if (actual_type == "CLOSED"sv) { |
185 | | // No-op. |
186 | 14.5k | } else if (actual_type == "UIDNEXT"sv) { |
187 | 244 | TRY(consume(" "sv)); |
188 | 242 | auto n = TRY(parse_number()); |
189 | 240 | m_response.data().set_uid_next(n); |
190 | 8.09k | } else if (actual_type == "UIDVALIDITY"sv) { |
191 | 194 | TRY(consume(" "sv)); |
192 | 192 | auto n = TRY(parse_number()); |
193 | 190 | m_response.data().set_uid_validity(n); |
194 | 7.90k | } else if (actual_type == "UNSEEN"sv) { |
195 | 210 | TRY(consume(" "sv)); |
196 | 208 | auto n = TRY(parse_number()); |
197 | 196 | m_response.data().set_unseen(n); |
198 | 7.69k | } else if (actual_type == "PERMANENTFLAGS"sv) { |
199 | 258 | TRY(consume(" "sv)); |
200 | 256 | auto flags = TRY(parse_list(+[](StringView x) { return ByteString(x); })); |
201 | 222 | m_response.data().set_permanent_flags(move(flags)); |
202 | 7.43k | } else if (actual_type == "HIGHESTMODSEQ"sv) { |
203 | 258 | TRY(consume(" "sv)); |
204 | 256 | TRY(parse_number()); |
205 | | // No-op for now. |
206 | 7.17k | } else { |
207 | 7.17k | dbgln("Unknown: {}", actual_type); |
208 | 204k | consume_while([](u8 x) { return x != ']'; }); |
209 | 7.17k | } |
210 | 45.5k | TRY(consume("]"sv)); |
211 | 45.5k | } |
212 | 28.2k | consume_until_end_of_line(); |
213 | 28.0k | TRY(consume("\r\n"sv)); |
214 | 1.13M | } else if (consume_if("SEARCH"sv)) { |
215 | 0 | Vector<unsigned> ids; |
216 | 0 | while (!consume_if("\r\n"sv)) { |
217 | 0 | TRY(consume(" "sv)); |
218 | 0 | auto id = TRY(parse_number()); |
219 | 0 | ids.append(id); |
220 | 0 | } |
221 | 0 | m_response.data().set_search_results(move(ids)); |
222 | 1.13M | } else if (consume_if("BYE"sv)) { |
223 | 6.78k | auto message = consume_until_end_of_line(); |
224 | 6.78k | TRY(consume("\r\n"sv)); |
225 | 6.78k | m_response.data().set_bye(message.is_empty() ? Optional<ByteString>() : Optional<ByteString>(message)); |
226 | 1.12M | } else if (consume_if("STATUS"sv)) { |
227 | 0 | TRY(consume(" "sv)); |
228 | 0 | auto mailbox = TRY(parse_astring()); |
229 | 0 | TRY(consume(" ("sv)); |
230 | 0 | auto status_item = StatusItem(); |
231 | 0 | status_item.set_mailbox(mailbox); |
232 | 0 | while (!consume_if(")"sv)) { |
233 | 0 | auto status_att = TRY(parse_atom()); |
234 | 0 | TRY(consume(" "sv)); |
235 | 0 | auto value = TRY(parse_number()); |
236 | |
|
237 | 0 | auto type = StatusItemType::Recent; |
238 | 0 | if (status_att == "MESSAGES"sv) { |
239 | 0 | type = StatusItemType::Messages; |
240 | 0 | } else if (status_att == "UNSEEN"sv) { |
241 | 0 | type = StatusItemType::Unseen; |
242 | 0 | } else if (status_att == "UIDNEXT"sv) { |
243 | 0 | type = StatusItemType::UIDNext; |
244 | 0 | } else if (status_att == "UIDVALIDITY"sv) { |
245 | 0 | type = StatusItemType::UIDValidity; |
246 | 0 | } else if (status_att == "RECENT"sv) { |
247 | 0 | type = StatusItemType::Recent; |
248 | 0 | } else { |
249 | 0 | dbgln("Unmatched status attribute: {}", status_att); |
250 | 0 | return Error::from_string_literal("Failed to parse status attribute"); |
251 | 0 | } |
252 | | |
253 | 0 | status_item.set(type, value); |
254 | |
|
255 | 0 | if (!at_end() && m_buffer[m_position] != ')') |
256 | 0 | TRY(consume(" "sv)); |
257 | 0 | } |
258 | 0 | m_response.data().add_status_item(move(status_item)); |
259 | 0 | consume_if(" "sv); // Not in the spec but the Outlook server sends a space for some reason. |
260 | 0 | TRY(consume("\r\n"sv)); |
261 | 1.12M | } else { |
262 | 1.12M | auto x = consume_until_end_of_line(); |
263 | 1.12M | TRY(consume("\r\n"sv)); |
264 | 1.12M | dbgln("ignored {}", x); |
265 | 1.12M | } |
266 | | |
267 | 1.87M | return {}; |
268 | 1.88M | } |
269 | | |
270 | | ErrorOr<StringView> Parser::parse_quoted_string() |
271 | 6.17M | { |
272 | 6.17M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, parse_quoted_string()", m_position); |
273 | 6.17M | auto start_position = m_position; |
274 | 31.2M | while (!at_end() && m_buffer[m_position] != '"') |
275 | 25.1M | switch (m_buffer[m_position]) { |
276 | | // https://datatracker.ietf.org/doc/html/rfc2683#section-3.4.2 |
277 | | // 3.4.2. Special Characters |
278 | | |
279 | | // Certain characters, currently the double-quote and the backslash, may |
280 | | // not be sent as-is inside a quoted string. These characters must be |
281 | | // preceded by the escape character if they are in a quoted string, or |
282 | | // else the string must be sent as a literal. |
283 | 28.1k | case '\\': |
284 | 28.1k | ++m_position; |
285 | 28.1k | if (at_end()) |
286 | 6 | return Error::from_string_literal("unterminated \\ escape"); |
287 | 28.1k | ++m_position; |
288 | 28.1k | break; |
289 | 25.0M | default: |
290 | 25.0M | ++m_position; |
291 | 25.1M | } |
292 | 6.17M | auto str = StringView(m_buffer.data() + start_position, m_position - start_position); |
293 | | // The CR and LF characters may be sent ONLY in literals; they are not |
294 | | // allowed, even if escaped, inside quoted strings. |
295 | 6.17M | if (str.contains("\r"sv)) |
296 | 124 | return Error::from_string_literal("CR character not allowed inside quoted string"); |
297 | 6.17M | if (str.contains("\n"sv)) |
298 | 80 | return Error::from_string_literal("LF character not allowed inside quoted string"); |
299 | 6.17M | TRY(consume("\""sv)); |
300 | 6.17M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret \"{}\"", m_position, str); |
301 | 6.17M | return str; |
302 | 6.17M | } |
303 | | |
304 | | ErrorOr<StringView> Parser::parse_string() |
305 | 6.17M | { |
306 | 6.17M | if (consume_if("\""sv)) |
307 | 6.17M | return parse_quoted_string(); |
308 | | |
309 | 1.81k | return parse_literal_string(); |
310 | 6.17M | } |
311 | | |
312 | | ErrorOr<StringView> Parser::parse_nstring() |
313 | 135k | { |
314 | 135k | dbgln_if(IMAP_PARSER_DEBUG, "p: {} parse_nstring()", m_position); |
315 | 135k | if (consume_if("NIL"sv)) |
316 | 9.80k | return StringView {}; |
317 | | |
318 | 125k | return { TRY(parse_string()) }; |
319 | 125k | } |
320 | | |
321 | | ErrorOr<FetchResponseData> Parser::parse_fetch_response() |
322 | 204k | { |
323 | 204k | TRY(consume(" ("sv)); |
324 | 204k | auto fetch_response = FetchResponseData(); |
325 | | |
326 | 470k | while (!consume_if(")"sv)) { |
327 | 270k | auto data_item = TRY(parse_fetch_data_item()); |
328 | 270k | switch (data_item.type) { |
329 | 29.7k | case FetchCommand::DataItemType::BodyStructure: { |
330 | 29.7k | TRY(consume(" ("sv)); |
331 | 29.7k | auto structure = TRY(parse_body_structure()); |
332 | 25.8k | fetch_response.set_body_structure(move(structure)); |
333 | 25.8k | break; |
334 | 29.7k | } |
335 | 0 | case FetchCommand::DataItemType::Envelope: { |
336 | 0 | TRY(consume(" "sv)); |
337 | 0 | fetch_response.set_envelope(TRY(parse_envelope())); |
338 | 0 | break; |
339 | 0 | } |
340 | 139k | case FetchCommand::DataItemType::Flags: { |
341 | 139k | TRY(consume(" "sv)); |
342 | 139k | auto flags = TRY(parse_list(+[](StringView x) { return ByteString(x); })); |
343 | 139k | fetch_response.set_flags(move(flags)); |
344 | 139k | break; |
345 | 139k | } |
346 | 0 | case FetchCommand::DataItemType::InternalDate: { |
347 | 0 | TRY(consume(" \""sv)); |
348 | 0 | auto date_view = consume_while([](u8 x) { return x != '"'; }); |
349 | 0 | TRY(consume("\""sv)); |
350 | 0 | auto date = Core::DateTime::parse("%d-%b-%Y %H:%M:%S %z"sv, date_view).value(); |
351 | 0 | fetch_response.set_internal_date(date); |
352 | 0 | break; |
353 | 0 | } |
354 | 90.6k | case FetchCommand::DataItemType::UID: { |
355 | 90.6k | TRY(consume(" "sv)); |
356 | 90.5k | fetch_response.set_uid(TRY(parse_number())); |
357 | 90.5k | break; |
358 | 90.5k | } |
359 | 0 | case FetchCommand::DataItemType::PeekBody: |
360 | | // Spec doesn't allow for this in a response. |
361 | 0 | return Error::from_string_literal("Unexpected fetch command type"); |
362 | 10.0k | case FetchCommand::DataItemType::BodySection: { |
363 | 10.0k | auto body = TRY(parse_nstring()); |
364 | 9.79k | fetch_response.add_body_data(move(data_item), body); |
365 | 9.79k | break; |
366 | 10.0k | } |
367 | 270k | } |
368 | 266k | if (!at_end() && m_buffer[m_position] != ')') |
369 | 92.4k | TRY(consume(" "sv)); |
370 | 266k | } |
371 | 204k | TRY(consume("\r\n"sv)); |
372 | 199k | return fetch_response; |
373 | 199k | } |
374 | | |
375 | | ErrorOr<Envelope> Parser::parse_envelope() |
376 | 0 | { |
377 | 0 | TRY(consume("("sv)); |
378 | 0 | auto date = TRY(parse_nstring()); |
379 | 0 | TRY(consume(" "sv)); |
380 | 0 | auto subject = TRY(parse_nstring()); |
381 | 0 | TRY(consume(" "sv)); |
382 | 0 | auto from = TRY(parse_address_list()); |
383 | 0 | TRY(consume(" "sv)); |
384 | 0 | auto sender = TRY(parse_address_list()); |
385 | 0 | TRY(consume(" "sv)); |
386 | 0 | auto reply_to = TRY(parse_address_list()); |
387 | 0 | TRY(consume(" "sv)); |
388 | 0 | auto to = TRY(parse_address_list()); |
389 | 0 | TRY(consume(" "sv)); |
390 | 0 | auto cc = TRY(parse_address_list()); |
391 | 0 | TRY(consume(" "sv)); |
392 | 0 | auto bcc = TRY(parse_address_list()); |
393 | 0 | TRY(consume(" "sv)); |
394 | 0 | auto in_reply_to = TRY(parse_nstring()); |
395 | 0 | TRY(consume(" "sv)); |
396 | 0 | auto message_id = TRY(parse_nstring()); |
397 | 0 | TRY(consume(")"sv)); |
398 | 0 | Envelope envelope = { |
399 | 0 | date, |
400 | 0 | subject, |
401 | 0 | from, |
402 | 0 | sender, |
403 | 0 | reply_to, |
404 | 0 | to, |
405 | 0 | cc, |
406 | 0 | bcc, |
407 | 0 | in_reply_to, |
408 | 0 | message_id |
409 | 0 | }; |
410 | 0 | return envelope; |
411 | 0 | } |
412 | | |
413 | | ErrorOr<BodyStructure> Parser::parse_body_structure() |
414 | 202k | { |
415 | 202k | if (!at_end() && m_buffer[m_position] == '(') { |
416 | 165k | auto data = MultiPartBodyStructureData(); |
417 | 176k | while (consume_if("("sv)) { |
418 | 172k | auto child = TRY(parse_body_structure()); |
419 | 11.4k | data.bodies.append(make<BodyStructure>(move(child))); |
420 | 11.4k | } |
421 | 165k | TRY(consume(" "sv)); |
422 | 7.46k | data.multipart_subtype = TRY(parse_string()); |
423 | | |
424 | 3.60k | if (!consume_if(")"sv)) { |
425 | 552 | TRY(consume(" "sv)); |
426 | 528 | data.params = consume_if("NIL"sv) ? HashMap<ByteString, ByteString> {} : TRY(parse_body_fields_params()); |
427 | 528 | if (!consume_if(")"sv)) { |
428 | 488 | TRY(consume(" "sv)); |
429 | 486 | if (!consume_if("NIL"sv)) { |
430 | 6 | data.disposition = { TRY(parse_disposition()) }; |
431 | 0 | } |
432 | | |
433 | 486 | if (!consume_if(")"sv)) { |
434 | 470 | TRY(consume(" "sv)); |
435 | 468 | if (!consume_if("NIL"sv)) { |
436 | 368 | data.langs = { TRY(parse_langs()) }; |
437 | 352 | } |
438 | | |
439 | 468 | if (!consume_if(")"sv)) { |
440 | 446 | TRY(consume(" "sv)); |
441 | 438 | data.location = consume_if("NIL"sv) ? ByteString {} : ByteString(TRY(parse_string())); |
442 | | |
443 | 438 | if (!consume_if(")"sv)) { |
444 | 428 | TRY(consume(" "sv)); |
445 | 412 | Vector<BodyExtension> extensions; |
446 | 24.0k | while (!consume_if(")"sv)) { |
447 | 23.7k | extensions.append(TRY(parse_body_extension())); |
448 | 23.6k | consume_if(" "sv); |
449 | 23.6k | } |
450 | 412 | data.extensions = { move(extensions) }; |
451 | 292 | } |
452 | 436 | } |
453 | 452 | } |
454 | 480 | } |
455 | 498 | } |
456 | | |
457 | 3.60k | return BodyStructure(move(data)); |
458 | 3.60k | } |
459 | | |
460 | 37.3k | return parse_one_part_body(); |
461 | 202k | } |
462 | | |
463 | | // body-type-1part |
464 | | ErrorOr<BodyStructure> Parser::parse_one_part_body() |
465 | 37.3k | { |
466 | | // NOTE: We share common parts between body-type-basic, body-type-msg and body-type-text types for readability. |
467 | 37.3k | BodyStructureData data; |
468 | | |
469 | | // media-basic / media-message / media-text |
470 | 37.3k | data.type = TRY(parse_string()); |
471 | 37.0k | TRY(consume(" "sv)); |
472 | 36.9k | data.subtype = TRY(parse_string()); |
473 | 36.9k | TRY(consume(" "sv)); |
474 | | |
475 | | // body-fields |
476 | 36.9k | data.fields = TRY(parse_body_fields_params()); |
477 | 35.7k | TRY(consume(" "sv)); |
478 | 35.7k | data.id = TRY(parse_nstring()); |
479 | 35.7k | TRY(consume(" "sv)); |
480 | 35.7k | data.desc = TRY(parse_nstring()); |
481 | 35.7k | TRY(consume(" "sv)); |
482 | 35.6k | data.encoding = TRY(parse_string()); |
483 | 35.6k | TRY(consume(" "sv)); |
484 | 35.6k | data.bytes = TRY(parse_number()); |
485 | | |
486 | 35.6k | if (data.type.equals_ignoring_ascii_case("TEXT"sv)) { |
487 | | // body-type-text |
488 | | // NOTE: "media-text SP body-fields" part is already parsed. |
489 | 0 | TRY(consume(" "sv)); |
490 | 0 | data.lines = TRY(parse_number()); |
491 | 35.6k | } else if (data.type.equals_ignoring_ascii_case("MESSAGE"sv) && data.subtype.is_one_of_ignoring_ascii_case("RFC822"sv, "GLOBAL"sv)) { |
492 | | // body-type-msg |
493 | | // NOTE: "media-message SP body-fields" part is already parsed. |
494 | 0 | TRY(consume(" "sv)); |
495 | 0 | auto envelope = TRY(parse_envelope()); |
496 | |
|
497 | 0 | TRY(consume(" ("sv)); |
498 | 0 | auto body = TRY(parse_body_structure()); |
499 | 0 | data.contanied_message = Tuple { move(envelope), make<BodyStructure>(move(body)) }; |
500 | |
|
501 | 0 | TRY(consume(" "sv)); |
502 | 0 | data.lines = TRY(parse_number()); |
503 | 35.6k | } else { |
504 | | // body-type-basic |
505 | | // NOTE: "media-basic SP body-fields" is already parsed. |
506 | 35.6k | } |
507 | | |
508 | 35.6k | if (!consume_if(")"sv)) { |
509 | 29.6k | TRY(consume(" "sv)); |
510 | | |
511 | | // body-ext-1part |
512 | 29.6k | TRY([&]() -> ErrorOr<void> { |
513 | 27.9k | data.md5 = TRY(parse_nstring()); |
514 | | |
515 | 27.9k | if (consume_if(")"sv)) |
516 | 27.9k | return {}; |
517 | 27.9k | TRY(consume(" "sv)); |
518 | 27.9k | if (!consume_if("NIL"sv)) { |
519 | 27.9k | data.disposition = { TRY(parse_disposition()) }; |
520 | 27.9k | } |
521 | | |
522 | 27.9k | if (consume_if(")"sv)) |
523 | 27.9k | return {}; |
524 | 27.9k | TRY(consume(" "sv)); |
525 | 27.9k | if (!consume_if("NIL"sv)) { |
526 | 27.9k | data.langs = { TRY(parse_langs()) }; |
527 | 27.9k | } |
528 | | |
529 | 27.9k | if (consume_if(")"sv)) |
530 | 27.9k | return {}; |
531 | 27.9k | TRY(consume(" "sv)); |
532 | 27.9k | data.location = TRY(parse_nstring()); |
533 | | |
534 | 27.9k | Vector<BodyExtension> extensions; |
535 | 27.9k | while (!consume_if(")"sv)) { |
536 | 27.9k | extensions.append(TRY(parse_body_extension())); |
537 | 27.9k | consume_if(" "sv); |
538 | 27.9k | } |
539 | 27.9k | data.extensions = { move(extensions) }; |
540 | 27.9k | return {}; |
541 | 27.9k | }()); |
542 | 27.9k | } |
543 | | |
544 | 35.6k | return BodyStructure(move(data)); |
545 | 35.6k | } |
546 | | |
547 | | ErrorOr<Vector<ByteString>> Parser::parse_langs() |
548 | 29.1k | { |
549 | 29.1k | AK::Vector<ByteString> langs; |
550 | 29.1k | if (!consume_if("("sv)) { |
551 | 28.8k | langs.append(TRY(parse_string())); |
552 | 28.6k | } else { |
553 | 1.04M | while (!consume_if(")"sv)) { |
554 | 1.04M | langs.append(TRY(parse_string())); |
555 | 1.04M | consume_if(" "sv); |
556 | 1.04M | } |
557 | 308 | } |
558 | 29.1k | return langs; |
559 | 29.1k | } |
560 | | |
561 | | ErrorOr<Tuple<ByteString, HashMap<ByteString, ByteString>>> Parser::parse_disposition() |
562 | 364 | { |
563 | 364 | TRY(consume("("sv)); |
564 | 230 | auto disposition_type = TRY(parse_string()); |
565 | 156 | TRY(consume(" "sv)); |
566 | 152 | auto disposition_vals = TRY(parse_body_fields_params()); |
567 | 148 | TRY(consume(")"sv)); |
568 | 142 | return Tuple<ByteString, HashMap<ByteString, ByteString>> { move(disposition_type), move(disposition_vals) }; |
569 | 148 | } |
570 | | |
571 | | ErrorOr<StringView> Parser::parse_literal_string() |
572 | 1.81k | { |
573 | 1.81k | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, parse_literal_string()", m_position); |
574 | 1.81k | TRY(consume("{"sv)); |
575 | 108 | auto num_bytes = TRY(parse_number()); |
576 | 104 | TRY(consume("}\r\n"sv)); |
577 | | |
578 | 98 | if (m_buffer.size() < m_position + num_bytes) { |
579 | 54 | dbgln("Attempted to parse string with length: {} at position {} (buffer length {})", num_bytes, m_position, m_position); |
580 | 54 | return Error::from_string_literal("Failed to parse string"); |
581 | 54 | } |
582 | | |
583 | 44 | m_position += num_bytes; |
584 | 44 | auto s = StringView(m_buffer.data() + m_position - num_bytes, num_bytes); |
585 | 44 | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret \"{}\"", m_position, s); |
586 | 44 | return s; |
587 | 98 | } |
588 | | |
589 | | ErrorOr<ListItem> Parser::parse_list_item() |
590 | 172k | { |
591 | 172k | TRY(consume(" "sv)); |
592 | 172k | auto flags_vec = TRY(parse_list(parse_mailbox_flag)); |
593 | 172k | unsigned flags = 0; |
594 | 5.50M | for (auto flag : flags_vec) { |
595 | 5.50M | flags |= static_cast<unsigned>(flag); |
596 | 5.50M | } |
597 | 172k | TRY(consume(" \""sv)); |
598 | 319k | auto reference = consume_while([](u8 x) { return x != '"'; }); |
599 | 172k | TRY(consume("\" "sv)); |
600 | 171k | auto mailbox = TRY(parse_astring()); |
601 | 171k | TRY(consume("\r\n"sv)); |
602 | 171k | return ListItem { flags, ByteString(reference), ByteString(mailbox) }; |
603 | 171k | } |
604 | | |
605 | | ErrorOr<void> Parser::parse_capability_response() |
606 | 536k | { |
607 | 536k | auto capability = AK::Vector<ByteString>(); |
608 | 538k | while (!consume_if("\r\n"sv)) { |
609 | 1.90k | TRY(consume(" "sv)); |
610 | 1.76k | capability.append(TRY(parse_atom())); |
611 | 1.74k | } |
612 | 536k | m_response.data().add_capabilities(move(capability)); |
613 | 536k | return {}; |
614 | 536k | } |
615 | | |
616 | | ErrorOr<StringView> Parser::parse_atom() |
617 | 551k | { |
618 | 551k | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, parse_atom()", m_position); |
619 | 5.61M | auto is_non_atom_char = [](u8 x) { |
620 | 5.61M | auto non_atom_chars = { '(', ')', '{', ' ', '%', '*', '"', '\\', ']' }; |
621 | 5.61M | return AK::find(non_atom_chars.begin(), non_atom_chars.end(), x) != non_atom_chars.end(); |
622 | 5.61M | }; |
623 | | |
624 | 551k | auto start = m_position; |
625 | 551k | auto count = 0; |
626 | 5.90M | while (!at_end() && !is_ascii_control(m_buffer[m_position]) && !is_non_atom_char(m_buffer[m_position])) { |
627 | 5.35M | count++; |
628 | 5.35M | m_position++; |
629 | 5.35M | } |
630 | | |
631 | 551k | if (count == 0) |
632 | 211 | return Error::from_string_literal("Invalid atom value"); |
633 | | |
634 | 551k | StringView s = StringView(m_buffer.data() + start, count); |
635 | 551k | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret \"{}\"", m_position, s); |
636 | 551k | return s; |
637 | 551k | } |
638 | | |
639 | | ErrorOr<ResponseStatus> Parser::parse_status() |
640 | 259 | { |
641 | 259 | auto atom = TRY(parse_atom()); |
642 | | |
643 | 238 | if (atom == "OK"sv) { |
644 | 112 | return ResponseStatus::OK; |
645 | 126 | } else if (atom == "BAD"sv) { |
646 | 2 | return ResponseStatus::Bad; |
647 | 124 | } else if (atom == "NO"sv) { |
648 | 34 | return ResponseStatus::No; |
649 | 34 | } |
650 | | |
651 | 90 | dbgln("Invalid ResponseStatus value: {}", atom); |
652 | 90 | return Error::from_string_literal("Failed to parse status type"); |
653 | 238 | } |
654 | | |
655 | | template<typename T> |
656 | | ErrorOr<Vector<T>> Parser::parse_list(T converter(StringView)) |
657 | 326k | { |
658 | 326k | TRY(consume("("sv)); |
659 | 326k | Vector<T> x; |
660 | 326k | bool first = true; |
661 | 190M | while (!consume_if(")"sv)) { |
662 | 190M | if (!first) |
663 | 190M | TRY(consume(" "sv)); |
664 | 236M | auto item = consume_while([](u8 x) { |
665 | 236M | return x != ' ' && x != ')'; |
666 | 236M | }); IMAP::Parser::parse_list<AK::ByteString>(AK::ByteString (*)(AK::StringView))::{lambda(unsigned char)#1}::operator()(unsigned char) constLine | Count | Source | 664 | 194M | auto item = consume_while([](u8 x) { | 665 | 194M | return x != ' ' && x != ')'; | 666 | 194M | }); |
IMAP::Parser::parse_list<IMAP::MailboxFlag>(IMAP::MailboxFlag (*)(AK::StringView))::{lambda(unsigned char)#1}::operator()(unsigned char) constLine | Count | Source | 664 | 41.7M | auto item = consume_while([](u8 x) { | 665 | 41.7M | return x != ' ' && x != ')'; | 666 | 41.7M | }); |
Unexecuted instantiation: IMAP::Parser::parse_list<AK::StringView>(AK::StringView (*)(AK::StringView))::{lambda(unsigned char)#1}::operator()(unsigned char) const |
667 | 190M | x.append(converter(item)); |
668 | 190M | first = false; |
669 | 190M | } |
670 | | |
671 | 326k | return x; |
672 | 326k | } AK::ErrorOr<AK::Vector<AK::ByteString, 0ul>, AK::Error> IMAP::Parser::parse_list<AK::ByteString>(AK::ByteString (*)(AK::StringView)) Line | Count | Source | 657 | 153k | { | 658 | 153k | TRY(consume("("sv)); | 659 | 153k | Vector<T> x; | 660 | 153k | bool first = true; | 661 | 179M | while (!consume_if(")"sv)) { | 662 | 179M | if (!first) | 663 | 179M | TRY(consume(" "sv)); | 664 | 179M | auto item = consume_while([](u8 x) { | 665 | 179M | return x != ' ' && x != ')'; | 666 | 179M | }); | 667 | 179M | x.append(converter(item)); | 668 | 179M | first = false; | 669 | 179M | } | 670 | | | 671 | 153k | return x; | 672 | 153k | } |
AK::ErrorOr<AK::Vector<IMAP::MailboxFlag, 0ul>, AK::Error> IMAP::Parser::parse_list<IMAP::MailboxFlag>(IMAP::MailboxFlag (*)(AK::StringView)) Line | Count | Source | 657 | 172k | { | 658 | 172k | TRY(consume("("sv)); | 659 | 172k | Vector<T> x; | 660 | 172k | bool first = true; | 661 | 11.5M | while (!consume_if(")"sv)) { | 662 | 11.3M | if (!first) | 663 | 11.2M | TRY(consume(" "sv)); | 664 | 11.3M | auto item = consume_while([](u8 x) { | 665 | 11.3M | return x != ' ' && x != ')'; | 666 | 11.3M | }); | 667 | 11.3M | x.append(converter(item)); | 668 | 11.3M | first = false; | 669 | 11.3M | } | 670 | | | 671 | 172k | return x; | 672 | 172k | } |
Unexecuted instantiation: AK::ErrorOr<AK::Vector<AK::StringView, 0ul>, AK::Error> IMAP::Parser::parse_list<AK::StringView>(AK::StringView (*)(AK::StringView)) |
673 | | |
674 | | MailboxFlag Parser::parse_mailbox_flag(StringView s) |
675 | 11.3M | { |
676 | 11.3M | if (s == "\\All"sv) |
677 | 1.48k | return MailboxFlag::All; |
678 | 11.3M | if (s == "\\Drafts"sv) |
679 | 3.08k | return MailboxFlag::Drafts; |
680 | 11.3M | if (s == "\\Flagged"sv) |
681 | 368 | return MailboxFlag::Flagged; |
682 | 11.3M | if (s == "\\HasChildren"sv) |
683 | 218 | return MailboxFlag::HasChildren; |
684 | 11.3M | if (s == "\\HasNoChildren"sv) |
685 | 358 | return MailboxFlag::HasNoChildren; |
686 | 11.3M | if (s == "\\Important"sv) |
687 | 292 | return MailboxFlag::Important; |
688 | 11.3M | if (s == "\\Junk"sv) |
689 | 768 | return MailboxFlag::Junk; |
690 | 11.3M | if (s == "\\Marked"sv) |
691 | 318 | return MailboxFlag::Marked; |
692 | 11.3M | if (s == "\\Noinferiors"sv) |
693 | 202 | return MailboxFlag::NoInferiors; |
694 | 11.3M | if (s == "\\Noselect"sv) |
695 | 232 | return MailboxFlag::NoSelect; |
696 | 11.3M | if (s == "\\Sent"sv) |
697 | 264 | return MailboxFlag::Sent; |
698 | 11.3M | if (s == "\\Trash"sv) |
699 | 312 | return MailboxFlag::Trash; |
700 | 11.3M | if (s == "\\Unmarked"sv) |
701 | 644 | return MailboxFlag::Unmarked; |
702 | | |
703 | 11.3M | dbgln("Unrecognized mailbox flag {}", s); |
704 | 11.3M | return MailboxFlag::Unknown; |
705 | 11.3M | } |
706 | | |
707 | | StringView Parser::consume_while(Function<bool(u8)> should_consume) |
708 | 192M | { |
709 | 192M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, consume_while()", m_position); |
710 | 192M | int chars = 0; |
711 | 325M | while (!at_end() && should_consume(m_buffer[m_position])) { |
712 | 133M | m_position++; |
713 | 133M | chars++; |
714 | 133M | } |
715 | 192M | auto s = StringView(m_buffer.data() + m_position - chars, chars); |
716 | | |
717 | 192M | dbgln_if(IMAP_PARSER_DEBUG, "p: {}, ret \"{}\"", m_position, s); |
718 | 192M | return s; |
719 | 192M | } |
720 | | |
721 | | StringView Parser::consume_until_end_of_line() |
722 | 1.15M | { |
723 | 80.6M | return consume_while([](u8 x) { return x != '\r'; }); |
724 | 1.15M | } |
725 | | |
726 | | ErrorOr<FetchCommand::DataItem> Parser::parse_fetch_data_item() |
727 | 270k | { |
728 | 1.45M | auto msg_attr = consume_while([](u8 x) { return is_ascii_alpha(x) != 0; }); |
729 | 270k | if (msg_attr.equals_ignoring_ascii_case("BODY"sv) && consume_if("["sv)) { |
730 | 10.2k | auto data_item = FetchCommand::DataItem { |
731 | 10.2k | .type = FetchCommand::DataItemType::BodySection, |
732 | 10.2k | .section = { {} } |
733 | 10.2k | }; |
734 | 6.20M | auto section_type = consume_while([](u8 x) { return x != ']' && x != ' '; }); |
735 | 10.2k | if (section_type.equals_ignoring_ascii_case("HEADER.FIELDS"sv)) { |
736 | 0 | data_item.section->type = FetchCommand::DataItem::SectionType::HeaderFields; |
737 | 0 | data_item.section->headers = Vector<ByteString>(); |
738 | 0 | TRY(consume(" "sv)); |
739 | 0 | auto headers = TRY(parse_list(+[](StringView x) { return x; })); |
740 | 0 | for (auto& header : headers) { |
741 | 0 | data_item.section->headers->append(header); |
742 | 0 | } |
743 | 0 | TRY(consume("]"sv)); |
744 | 10.2k | } else if (section_type.equals_ignoring_ascii_case("HEADER.FIELDS.NOT"sv)) { |
745 | 0 | data_item.section->type = FetchCommand::DataItem::SectionType::HeaderFieldsNot; |
746 | 0 | data_item.section->headers = Vector<ByteString>(); |
747 | 0 | TRY(consume(" ("sv)); |
748 | 0 | auto headers = TRY(parse_list(+[](StringView x) { return x; })); |
749 | 0 | for (auto& header : headers) { |
750 | 0 | data_item.section->headers->append(header); |
751 | 0 | } |
752 | 0 | TRY(consume("]"sv)); |
753 | 10.2k | } else if (is_ascii_digit(section_type[0])) { |
754 | 10.1k | data_item.section->type = FetchCommand::DataItem::SectionType::Parts; |
755 | 10.1k | data_item.section->parts = Vector<unsigned>(); |
756 | | |
757 | 10.1k | while (!consume_if("]"sv)) { |
758 | 98 | auto num = try_parse_number(); |
759 | 98 | if (num.has_value()) { |
760 | 0 | data_item.section->parts->append(num.value()); |
761 | 0 | continue; |
762 | 0 | } |
763 | 98 | auto atom = TRY(parse_atom()); |
764 | 0 | if (atom.equals_ignoring_ascii_case("MIME"sv)) { |
765 | 0 | data_item.section->ends_with_mime = true; |
766 | 0 | continue; |
767 | 0 | } |
768 | 0 | } |
769 | 10.1k | } else if (section_type.equals_ignoring_ascii_case("TEXT"sv)) { |
770 | 0 | data_item.section->type = FetchCommand::DataItem::SectionType::Text; |
771 | 124 | } else if (section_type.equals_ignoring_ascii_case("HEADER"sv)) { |
772 | 0 | data_item.section->type = FetchCommand::DataItem::SectionType::Header; |
773 | 124 | } else { |
774 | 124 | dbgln("Unmatched section type {}", section_type); |
775 | 124 | return Error::from_string_literal("Failed to parse section type"); |
776 | 124 | } |
777 | 10.0k | if (consume_if("<"sv)) { |
778 | 6 | auto start = TRY(parse_number()); |
779 | 4 | data_item.partial_fetch = true; |
780 | 4 | data_item.start = (int)start; |
781 | 4 | TRY(consume(">"sv)); |
782 | 2 | } |
783 | 10.0k | consume_if(" "sv); |
784 | 10.0k | return data_item; |
785 | 260k | } else if (msg_attr.equals_ignoring_ascii_case("FLAGS"sv)) { |
786 | 139k | return FetchCommand::DataItem { |
787 | 139k | .type = FetchCommand::DataItemType::Flags |
788 | 139k | }; |
789 | 139k | } else if (msg_attr.equals_ignoring_ascii_case("UID"sv)) { |
790 | 90.6k | return FetchCommand::DataItem { |
791 | 90.6k | .type = FetchCommand::DataItemType::UID |
792 | 90.6k | }; |
793 | 90.6k | } else if (msg_attr.equals_ignoring_ascii_case("INTERNALDATE"sv)) { |
794 | 0 | return FetchCommand::DataItem { |
795 | 0 | .type = FetchCommand::DataItemType::InternalDate |
796 | 0 | }; |
797 | 30.1k | } else if (msg_attr.equals_ignoring_ascii_case("ENVELOPE"sv)) { |
798 | 0 | return FetchCommand::DataItem { |
799 | 0 | .type = FetchCommand::DataItemType::Envelope |
800 | 0 | }; |
801 | 30.1k | } else if (msg_attr.equals_ignoring_ascii_case("BODY"sv) || msg_attr.equals_ignoring_ascii_case("BODYSTRUCTURE"sv)) { |
802 | 29.7k | return FetchCommand::DataItem { |
803 | 29.7k | .type = FetchCommand::DataItemType::BodyStructure |
804 | 29.7k | }; |
805 | 29.7k | } else { |
806 | 372 | dbgln("msg_attr not matched: {}", msg_attr); |
807 | 372 | return Error::from_string_literal("Failed to parse msg_attr"); |
808 | 372 | } |
809 | 270k | } |
810 | | |
811 | | ErrorOr<Vector<Address>> Parser::parse_address_list() |
812 | 0 | { |
813 | 0 | if (consume_if("NIL"sv)) |
814 | 0 | return Vector<Address> {}; |
815 | | |
816 | 0 | auto addresses = Vector<Address>(); |
817 | 0 | TRY(consume("("sv)); |
818 | 0 | while (!consume_if(")"sv)) |
819 | 0 | addresses.append(TRY(parse_address())); |
820 | 0 | return { addresses }; |
821 | 0 | } |
822 | | |
823 | | ErrorOr<Address> Parser::parse_address() |
824 | 0 | { |
825 | 0 | TRY(consume("("sv)); |
826 | 0 | auto address = Address(); |
827 | 0 | auto name = TRY(parse_nstring()); |
828 | 0 | address.name = name; |
829 | 0 | TRY(consume(" "sv)); |
830 | 0 | auto source_route = TRY(parse_nstring()); |
831 | 0 | address.source_route = source_route; |
832 | 0 | TRY(consume(" "sv)); |
833 | 0 | auto mailbox = TRY(parse_nstring()); |
834 | 0 | address.mailbox = mailbox; |
835 | 0 | TRY(consume(" "sv)); |
836 | 0 | auto host = TRY(parse_nstring()); |
837 | 0 | address.host = host; |
838 | 0 | TRY(consume(")"sv)); |
839 | | // [RFC-2822] group syntax is indicated by a special form of |
840 | | // address structure in which the host name field is NIL. If the |
841 | | // mailbox name field is also NIL, this is an end of group marker |
842 | | // (semi-colon in RFC 822 syntax). If the mailbox name field is |
843 | | // non-NIL, this is a start of group marker, and the mailbox name |
844 | | // field holds the group name phrase. |
845 | 0 | if (!address.mailbox.is_empty() && address.host.is_empty()) { |
846 | | // FIXME: Implement Group addresses per RFC-2822. For now, we just consume the group |
847 | | // members, and return an Address object with the group name phrase in the mailbox field. |
848 | 0 | auto group_address = TRY(parse_address()); |
849 | 0 | while (!group_address.mailbox.is_empty() && !group_address.host.is_empty()) |
850 | 0 | group_address = TRY(parse_address()); |
851 | 0 | } |
852 | 0 | return address; |
853 | 0 | } |
854 | | |
855 | | ErrorOr<StringView> Parser::parse_astring() |
856 | 171k | { |
857 | 171k | if (!at_end() && (m_buffer[m_position] == '{' || m_buffer[m_position] == '"')) |
858 | 52.9k | return parse_string(); |
859 | | |
860 | 118k | return parse_atom(); |
861 | 171k | } |
862 | | |
863 | | ErrorOr<HashMap<ByteString, ByteString>> Parser::parse_body_fields_params() |
864 | 37.1k | { |
865 | 37.1k | if (consume_if("NIL"sv)) |
866 | 66 | return HashMap<ByteString, ByteString> {}; |
867 | | |
868 | 37.0k | HashMap<ByteString, ByteString> fields; |
869 | 37.0k | TRY(consume("("sv)); |
870 | 47.1k | while (!consume_if(")"sv)) { |
871 | 11.2k | auto key = TRY(parse_string()); |
872 | 10.6k | TRY(consume(" "sv)); |
873 | 10.4k | auto value = TRY(parse_string()); |
874 | 10.2k | fields.set(key, value); |
875 | 10.2k | consume_if(" "sv); |
876 | 10.2k | } |
877 | | |
878 | 36.8k | return fields; |
879 | 36.8k | } |
880 | | |
881 | | ErrorOr<BodyExtension> Parser::parse_body_extension() |
882 | 4.87M | { |
883 | 4.87M | if (consume_if("NIL"sv)) |
884 | 57.9k | return BodyExtension { Optional<ByteString> {} }; |
885 | | |
886 | 4.81M | if (consume_if("("sv)) { |
887 | 9.41k | Vector<OwnPtr<BodyExtension>> extensions; |
888 | 3.68M | while (!consume_if(")"sv)) { |
889 | 3.68M | extensions.append(make<BodyExtension>(TRY(parse_body_extension()))); |
890 | 3.67M | consume_if(" "sv); |
891 | 3.67M | } |
892 | 9.41k | return BodyExtension { move(extensions) }; |
893 | 9.41k | } |
894 | | |
895 | 4.80M | if (!at_end() && (m_buffer[m_position] == '"' || m_buffer[m_position] == '{')) |
896 | 4.78M | return BodyExtension { { TRY(parse_string()) } }; |
897 | | |
898 | 17.1k | return BodyExtension { TRY(parse_number()) }; |
899 | 17.1k | } |
900 | | |
901 | | } |