175 lines
4.1 KiB
TypeScript
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;
|
|
}
|