Coverage Report

Created: 2025-12-18 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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) const
Line
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) const
Line
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
}