export type ImapCommandParameters = string | string[] | Record; export function parseImapCommand(commandString: string): { tag: string; command: string; parameters: ImapCommandParameters[]; } { const parts: string[] = []; let currentPart = ""; let inQuotes = false; let parenDepth = 0; let bracketDepth = 0; for (let i = 0; i < commandString.length; i++) { const char = commandString[i]; if (char === '"' && (i === 0 || commandString[i - 1] !== "\\")) { inQuotes = !inQuotes; currentPart += char; } else if (char === "(" && !inQuotes) { parenDepth++; currentPart += char; } else if (char === ")" && !inQuotes) { parenDepth--; currentPart += char; } else if (char === "[" && !inQuotes) { bracketDepth++; currentPart += char; } else if (char === "]" && !inQuotes) { bracketDepth--; currentPart += char; } else if ( char === " " && !inQuotes && parenDepth === 0 && bracketDepth === 0 ) { if (currentPart) { parts.push(currentPart); currentPart = ""; } } else { currentPart += char; } } if (currentPart) { parts.push(currentPart); } if (parts.length < 2) { throw new Error("Invalid command"); } const tag = parts[0]; let command = parts[1]; let paramStartIdx = 2; if (command.toUpperCase() === "UID") { command = `${command} ${parts[2]}`; paramStartIdx = 3; } const parameters: ImapCommandParameters[] = []; for (let i = paramStartIdx; i < parts.length; i++) { let part = parts[i]; if (part.startsWith('"') && part.endsWith('"')) { part = part.slice(1, -1); } if (part.includes('\\"')) { parameters.push(part.split('\\"')); } else if (part.startsWith("(") && part.endsWith(")")) { parameters.push(parseDataItems(part)); } else { parameters.push(part); } } return { tag, command, parameters, }; } export function parseDataItems( dataItemsString: string, ): ImapCommandParameters[] { const inner = dataItemsString.slice(1, -1).trim(); if (!inner) return []; const result: ImapCommandParameters[] = []; let current = ""; let inQuotes = false; let parenDepth = 0; let bracketDepth = 0; let fullSectionId = ""; for (let i = 0; i < inner.length; i++) { const char = inner[i]; if (char === '"' && (i === 0 || inner[i - 1] !== "\\")) { inQuotes = !inQuotes; current += char; } else if (char === "[" && !inQuotes) { bracketDepth++; if ( current.toUpperCase().startsWith("BODY.PEEK") || current.toUpperCase() === "BODY" ) { fullSectionId = current + char; current += char; } else { current += char; } } else if (char === "(" && !inQuotes) { parenDepth++; if (bracketDepth > 0 && fullSectionId) { fullSectionId = current; current = ""; } else { current += char; } } else if (char === ")" && !inQuotes) { parenDepth--; if (parenDepth === 0 && bracketDepth > 0 && fullSectionId) { const fieldList = current.split(/\s+/).filter(Boolean); current = fieldList; } else { current += char; } } else if (char === "]" && !inQuotes) { bracketDepth--; if (bracketDepth === 0 && fullSectionId) { if (Array.isArray(current)) { const sectionObj: Record = {}; sectionObj[`${fullSectionId.trim()}]`] = current; result.push(sectionObj); } else { result.push(`${fullSectionId}${current}]`); } fullSectionId = ""; current = ""; } else if (fullSectionId) { current += char; } else { current += char; } } else if ( char === " " && !inQuotes && parenDepth === 0 && bracketDepth === 0 ) { if (current) { result.push(current); current = ""; } } else { if (!Array.isArray(current)) { current += char; } } } if (current && !Array.isArray(current)) { result.push(current); } return result; }