nostr-mailing-list/imapParsing.ts

175 lines
4.1 KiB
TypeScript

export type ImapCommandParameters = string | string[] | Record<string, any>;
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<string, string[]> = {};
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;
}