diff --git a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Mailbox.swift b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Mailbox.swift index 5f8051f5c..457a7033e 100644 --- a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Mailbox.swift +++ b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Mailbox.swift @@ -35,66 +35,97 @@ extension GrammarParser { // "STATUS" SP mailbox SP "(" [status-att-list] ")" / // number SP "EXISTS" / Namespace-Response func parseMailboxData(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - func parseMailboxData_flags(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("FLAGS ", buffer: &buffer, tracker: tracker) - return .flags(try self.parseFlagList(buffer: &buffer, tracker: tracker)) - } + /// Parse those sub-parses that have a fixed text prefix. + func parseMailboxData_withFixedPrefix(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + func parseMailboxData_flags(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + return .flags(try self.parseFlagList(buffer: &buffer, tracker: tracker)) + } - func parseMailboxData_list(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("LIST ", buffer: &buffer, tracker: tracker) - return .list(try self.parseMailboxList(buffer: &buffer, tracker: tracker)) - } + func parseMailboxData_list(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + return .list(try self.parseMailboxList(buffer: &buffer, tracker: tracker)) + } - func parseMailboxData_lsub(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("LSUB ", buffer: &buffer, tracker: tracker) - return .lsub(try self.parseMailboxList(buffer: &buffer, tracker: tracker)) - } + func parseMailboxData_lsub(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + return .lsub(try self.parseMailboxList(buffer: &buffer, tracker: tracker)) + } - func parseMailboxData_extendedSearch(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - let response = try self.parseExtendedSearchResponse(buffer: &buffer, tracker: tracker) - return .extendedSearch(response) - } + func parseMailboxData_extendedSearch(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + let response = try self.parseExtendedSearchResponse(buffer: &buffer, tracker: tracker) + return .extendedSearch(response) + } - func parseMailboxData_search(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("SEARCH", buffer: &buffer, tracker: tracker) - let nums = try PL.parseZeroOrMore(buffer: &buffer, tracker: tracker) { - (buffer, tracker) -> UnknownMessageIdentifier in - try PL.parseSpaces(buffer: &buffer, tracker: tracker) - let num = try self.parseNZNumber(buffer: &buffer, tracker: tracker) - guard let id = UnknownMessageIdentifier(exactly: num) else { - throw ParserError(hint: "Can't make unknown message identfiier from \(num)") + + func parseMailboxData_search_combined(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + func parseMailboxData_search(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + let nums = try PL.parseZeroOrMore(buffer: &buffer, tracker: tracker) { + (buffer, tracker) -> UnknownMessageIdentifier in + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + let num = try self.parseNZNumber(buffer: &buffer, tracker: tracker) + guard let id = UnknownMessageIdentifier(exactly: num) else { + throw ParserError(hint: "Can't make unknown message identfiier from \(num)") + } + return id + } + return .search(nums) } - return id - } - return .search(nums) - } - func parseMailboxData_searchSort(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("SEARCH", buffer: &buffer, tracker: tracker) - try PL.parseSpaces(buffer: &buffer, tracker: tracker) - var array = [try self.parseNZNumber(buffer: &buffer, tracker: tracker)] - try PL.parseZeroOrMore( - buffer: &buffer, - into: &array, - tracker: tracker, - parser: { (buffer, tracker) in + func parseMailboxData_searchSort(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { try PL.parseSpaces(buffer: &buffer, tracker: tracker) - return try self.parseNZNumber(buffer: &buffer, tracker: tracker) + var array = [try self.parseNZNumber(buffer: &buffer, tracker: tracker)] + try PL.parseZeroOrMore( + buffer: &buffer, + into: &array, + tracker: tracker, + parser: { (buffer, tracker) in + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + return try self.parseNZNumber(buffer: &buffer, tracker: tracker) + } + ) + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + let seq = try self.parseSearchSortModificationSequence(buffer: &buffer, tracker: tracker) + return .searchSort(.init(identifiers: array, modificationSequence: seq)) } - ) - try PL.parseSpaces(buffer: &buffer, tracker: tracker) - let seq = try self.parseSearchSortModificationSequence(buffer: &buffer, tracker: tracker) - return .searchSort(.init(identifiers: array, modificationSequence: seq)) - } - func parseMailboxData_status(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - try PL.parseFixedString("STATUS ", buffer: &buffer, tracker: tracker) - let mailbox = try self.parseMailbox(buffer: &buffer, tracker: tracker) - try PL.parseSpaces(buffer: &buffer, tracker: tracker) - try PL.parseFixedString("(", buffer: &buffer, tracker: tracker) - let status = try PL.parseOptional(buffer: &buffer, tracker: tracker, parser: self.parseMailboxStatus) - try PL.parseFixedString(")", buffer: &buffer, tracker: tracker) - return .status(mailbox, status ?? .init()) + return try PL.parseOneOf( + parseMailboxData_searchSort, + parseMailboxData_search, + buffer: &buffer, + tracker: tracker + ) + } + + func parseMailboxData_status(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + let mailbox = try self.parseMailbox(buffer: &buffer, tracker: tracker) + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + try PL.parseFixedString("(", buffer: &buffer, tracker: tracker) + let status = try PL.parseOptional(buffer: &buffer, tracker: tracker, parser: self.parseMailboxStatus) + try PL.parseFixedString(")", buffer: &buffer, tracker: tracker) + return .status(mailbox, status ?? .init()) + } + + func parseMailboxData_namespace(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + .namespace(try self.parseNamespaceResponse(buffer: &buffer, tracker: tracker)) + } + + func parseMailboxData_uidBatchesResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { + .uidBatches(try self.parseUIDBatchesResponse(buffer: &buffer, tracker: tracker)) + } + + let commandParsers: [String: (inout ParseBuffer, StackTracker) throws -> MailboxData] = [ + "FLAGS": parseMailboxData_flags, + "LIST": parseMailboxData_list, + "LSUB": parseMailboxData_lsub, + "ESEARCH": parseMailboxData_extendedSearch, + "SEARCH": parseMailboxData_search_combined, + "STATUS": parseMailboxData_status, + "NAMESPACE": parseMailboxData_namespace, + "UIDBATCHES": parseMailboxData_uidBatchesResponse, + ] + return try parseFromLookupTable(buffer: &buffer, tracker: tracker, parsers: commandParsers) } func parseMailboxData_exists(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { @@ -109,27 +140,11 @@ extension GrammarParser { return .recent(number) } - func parseMailboxData_namespace(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - .namespace(try self.parseNamespaceResponse(buffer: &buffer, tracker: tracker)) - } - - func parseMailboxData_uidBatchesResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> MailboxData { - .uidBatches(try self.parseUIDBatchesResponse(buffer: &buffer, tracker: tracker)) - } - return try PL.parseOneOf( [ - parseMailboxData_flags, - parseMailboxData_list, - parseMailboxData_lsub, - parseMailboxData_extendedSearch, - parseMailboxData_status, + parseMailboxData_withFixedPrefix, parseMailboxData_exists, parseMailboxData_recent, - parseMailboxData_searchSort, - parseMailboxData_search, - parseMailboxData_namespace, - parseMailboxData_uidBatchesResponse, ], buffer: &buffer, tracker: tracker diff --git a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Response.swift b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Response.swift index 3de6396dd..516cae228 100644 --- a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Response.swift +++ b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser+Response.swift @@ -359,7 +359,7 @@ extension GrammarParser { } func parseSuffix_namespace(buffer: inout ParseBuffer, tracker: StackTracker) throws -> ResponseTextCode { - .namespace(try self.parseNamespaceSuffix(buffer: &buffer, tracker: tracker)) + .namespace(try self.parseNamespaceResponse(buffer: &buffer, tracker: tracker)) } func parseResponseTextCode_atom(buffer: inout ParseBuffer, tracker: StackTracker) throws -> ResponseTextCode { diff --git a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser.swift b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser.swift index cccf777b7..520bd9cab 100644 --- a/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser.swift +++ b/Sources/NIOIMAPCore/Parser/Grammar/GrammarParser.swift @@ -521,7 +521,6 @@ extension GrammarParser { func parseExtendedSearchResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> ExtendedSearchResponse { try PL.composite(buffer: &buffer, tracker: tracker) { (buffer, tracker) in - try PL.parseFixedString("ESEARCH", buffer: &buffer, tracker: tracker) let correlator = try PL.parseOptional(buffer: &buffer, tracker: tracker, parser: self.parseSearchCorrelator) let kind: ExtendedSearchResponse.Kind = try PL.parseOptional(buffer: &buffer, tracker: tracker) { (buffer, tracker) in @@ -1821,17 +1820,8 @@ extension GrammarParser { } } - // Namespace-Response = "*" SP "NAMESPACE" SP Namespace - // SP Namespace SP Namespace - func parseNamespaceResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> NamespaceResponse { - try PL.composite(buffer: &buffer, tracker: tracker) { buffer, tracker -> NamespaceResponse in - try PL.parseFixedString("NAMESPACE", buffer: &buffer, tracker: tracker) - return try parseNamespaceSuffix(buffer: &buffer, tracker: tracker) - } - } - // SP Namespace SP Namespace SP Namespace - func parseNamespaceSuffix(buffer: inout ParseBuffer, tracker: StackTracker) throws -> NamespaceResponse { + func parseNamespaceResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> NamespaceResponse { try PL.composite(buffer: &buffer, tracker: tracker) { buffer, tracker -> NamespaceResponse in try PL.parseSpaces(buffer: &buffer, tracker: tracker) let n1 = try self.parseNamespace(buffer: &buffer, tracker: tracker) @@ -1847,7 +1837,8 @@ extension GrammarParser { // [SP uid-range *("," uid-range) ] func parseUIDBatchesResponse(buffer: inout ParseBuffer, tracker: StackTracker) throws -> UIDBatchesResponse { try PL.composite(buffer: &buffer, tracker: tracker) { buffer, tracker -> UIDBatchesResponse in - try PL.parseFixedString(#"UIDBATCHES (TAG ""#, buffer: &buffer, tracker: tracker) + try PL.parseSpaces(buffer: &buffer, tracker: tracker) + try PL.parseFixedString(#"(TAG ""#, buffer: &buffer, tracker: tracker) let tag = try self.parseTag(buffer: &buffer, tracker: tracker) try PL.parseFixedString("\")", buffer: &buffer, tracker: tracker) let batches = try PL.parseOptional(buffer: &buffer, tracker: tracker) { buffer, tracker -> [UIDRange] in diff --git a/Tests/NIOIMAPCoreTests/Parser/Grammar/GrammarParser+Mailbox+Tests.swift b/Tests/NIOIMAPCoreTests/Parser/Grammar/GrammarParser+Mailbox+Tests.swift index 8ccef4faa..eff8685ff 100644 --- a/Tests/NIOIMAPCoreTests/Parser/Grammar/GrammarParser+Mailbox+Tests.swift +++ b/Tests/NIOIMAPCoreTests/Parser/Grammar/GrammarParser+Mailbox+Tests.swift @@ -52,6 +52,7 @@ extension GrammarParser_Mailbox_Tests { .extendedSearch(.init(correlator: nil, kind: .sequenceNumber, returnData: [.min(1), .max(2)])), #line ), + ("ESEARCH", "\r", .extendedSearch(.init(correlator: nil, kind: .sequenceNumber, returnData: [])), #line), ("1234 EXISTS", "\r\n", .exists(1234), #line), ("5678 RECENT", "\r\n", .recent(5678), #line), ("STATUS INBOX ()", "\r\n", .status(.inbox, .init()), #line), diff --git a/Tests/NIOIMAPCoreTests/Parser/IMAPParserTests.swift b/Tests/NIOIMAPCoreTests/Parser/IMAPParserTests.swift index d20b9293b..ba8b45d2d 100644 --- a/Tests/NIOIMAPCoreTests/Parser/IMAPParserTests.swift +++ b/Tests/NIOIMAPCoreTests/Parser/IMAPParserTests.swift @@ -1455,18 +1455,18 @@ extension ParserUnitTests { self.iterateTests( testFunction: GrammarParser().parseExtendedSearchResponse, validInputs: [ - ("ESEARCH", "\r", .init(correlator: nil, kind: .sequenceNumber, returnData: []), #line), - ("ESEARCH UID", "\r", .init(correlator: nil, kind: .uid, returnData: []), #line), + ("", "\r", .init(correlator: nil, kind: .sequenceNumber, returnData: []), #line), + (" UID", "\r", .init(correlator: nil, kind: .uid, returnData: []), #line), ( - "ESEARCH (TAG \"col\") UID", "\r", + " (TAG \"col\") UID", "\r", .init(correlator: SearchCorrelator(tag: "col"), kind: .uid, returnData: []), #line ), ( - "ESEARCH (TAG \"col\") UID COUNT 2", "\r", + " (TAG \"col\") UID COUNT 2", "\r", .init(correlator: SearchCorrelator(tag: "col"), kind: .uid, returnData: [.count(2)]), #line ), ( - "ESEARCH (TAG \"col\") UID MIN 1 MAX 2", "\r", + " (TAG \"col\") UID MIN 1 MAX 2", "\r", .init(correlator: SearchCorrelator(tag: "col"), kind: .uid, returnData: [.min(1), .max(2)]), #line ), ], @@ -1483,12 +1483,12 @@ extension ParserUnitTests { self.iterateTests( testFunction: GrammarParser().parseUIDBatchesResponse, validInputs: [ - (#"UIDBATCHES (TAG "A143") 215295:99695,99696:20350,20351:7829,7830:1"#, "\r", .init(correlator: "A143", batches: [ + (#" (TAG "A143") 215295:99695,99696:20350,20351:7829,7830:1"#, "\r", .init(correlator: "A143", batches: [ 99695...215295, 20350...99696, 7829...20351, 1...7830, ]), #line), - (#"UIDBATCHES (TAG "A143")"#, "\r", .init(correlator: "A143", batches: []), #line), - (#"UIDBATCHES (TAG "A143") 99695"#, "\r", .init(correlator: "A143", batches: [99695...99695]), #line), - (#"UIDBATCHES (TAG "A143") 20350:20350"#, "\r", .init(correlator: "A143", batches: [20350...20350]), #line), + (#" (TAG "A143")"#, "\r", .init(correlator: "A143", batches: []), #line), + (#" (TAG "A143") 99695"#, "\r", .init(correlator: "A143", batches: [99695...99695]), #line), + (#" (TAG "A143") 20350:20350"#, "\r", .init(correlator: "A143", batches: [20350...20350]), #line), ], parserErrorInputs: [], incompleteMessageInputs: [] @@ -2318,7 +2318,7 @@ extension ParserUnitTests { testFunction: GrammarParser().parseNamespaceResponse, validInputs: [ ( - "NAMESPACE nil nil nil", " ", .init(userNamespace: [], otherUserNamespace: [], sharedNamespace: []), + " nil nil nil", " ", .init(userNamespace: [], otherUserNamespace: [], sharedNamespace: []), #line ) ],