feat: implement vacation responder functionality with UI integration and localization

This commit is contained in:
Linus Rath
2026-03-27 18:32:02 +01:00
parent b70c727bae
commit 42734f16c3
4 changed files with 93 additions and 2 deletions
+45 -1
View File
@@ -40,9 +40,53 @@ function isValidRule(rule: unknown): rule is FilterRule {
return r.conditions.every(isValidCondition) && r.actions.every(isValidAction);
}
/**
* Detect Stalwart-generated vacation-only scripts (no metadata).
* These contain `vacation` command but no other filter logic we need to preserve.
*/
function detectVacationOnlyScript(content: string): ParseResult | null {
// Must contain a vacation command
if (!/\bvacation\b/.test(content)) return null;
// Strip requires, comments, and whitespace to see if only vacation remains
const stripped = content
.replace(/^\s*require\s+\[[^\]]*\]\s*;/gm, '')
.replace(/#[^\n]*/g, '')
.replace(/\/\*[\s\S]*?\*\//g, '')
.trim();
// After stripping, the only meaningful statement should be a vacation command.
// Check there are no if/elsif/else blocks (i.e., no filter rules).
if (/\b(?:if|elsif|else)\b/.test(stripped)) return null;
// Extract subject if present
const subjectMatch = stripped.match(/:subject\s+"((?:[^"\\]|\\.)*)"/);
const subject = subjectMatch ? subjectMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\') : '';
// Extract the body text (last quoted string in the vacation command)
// Stalwart uses MIME format; extract the plain text after the MIME headers
const mimeBodyMatch = stripped.match(/Content-Transfer-Encoding:[^\n]*\n\n([\s\S]*?)"\s*;\s*$/);
const simpleBodyMatch = stripped.match(/vacation[^;]*"((?:[^"\\]|\\.)*)"\s*;\s*$/);
let textBody = '';
if (mimeBodyMatch) {
textBody = mimeBodyMatch[1].trim();
} else if (simpleBodyMatch) {
textBody = simpleBodyMatch[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
return {
rules: [],
isOpaque: false,
vacation: { isEnabled: true, subject, textBody },
};
}
export function parseScript(content: string): ParseResult {
const beginIdx = content.indexOf(METADATA_BEGIN);
if (beginIdx === -1) return OPAQUE;
if (beginIdx === -1) {
// No metadata — check if it's a Stalwart vacation-only script
return detectVacationOnlyScript(content) || OPAQUE;
}
const endIdx = content.indexOf(METADATA_END, beginIdx);
if (endIdx === -1) return OPAQUE;