From 04fe2b53f83b310ae013285ff9e96fb24ce1e60a Mon Sep 17 00:00:00 2001 From: Yatin Tekumalla Date: Tue, 28 Apr 2026 19:11:39 +0530 Subject: [PATCH] Add support for all RWZ rule element types from the spec The original parser only handled ~15 rule element IDs and would crash on most real-world .rwz files. This adds support for ~60 element types covering all conditions, actions, and exceptions documented in the OutlookRulesReader spec by @hughbe. Changes: - Add 8 new data parser classes: ImportanceRuleElementData, SensitivityRuleElementData, CategoriesListRuleElementData, PathRuleElementData, OnThisComputerOnlyRuleElementData, SizeInSpecificRangeRuleElementData, DisplayMessageRuleElementData, DeferDeliveryRuleElementData - Expand the RuleElement switch from 15 to 60+ element IDs - Accept input/output paths as CLI arguments instead of hardcoded paths - Improve error message for unknown element types to show hex ID --- src/index.ts | 282 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 190 insertions(+), 92 deletions(-) diff --git a/src/index.ts b/src/index.ts index e35b188..d3e79ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -530,7 +530,104 @@ class StringsListRuleElementData extends RuleElementData { } class FlaggedForActionRuleElementData extends RuleElementData { +} + +class ImportanceRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public importance: number; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.importance = sb.readUInt32(); + } +} + +class SensitivityRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public sensitivity: number; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.sensitivity = sb.readUInt32(); + } +} + +class CategoriesListRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public categories: string; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.categories = sb.readStringObject(); + } +} + +class PathRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public path: string; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.path = sb.readStringObject(); + } +} + +class OnThisComputerOnlyRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public uuid: string; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.uuid = sb.readAsciiString(16); + } +} + +class SizeInSpecificRangeRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public min: number; + public max: number; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.min = sb.readUInt32(); + this.max = sb.readUInt32(); + } +} + +class DisplayMessageRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public message: string; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.message = sb.readStringObject(); + } +} +class DeferDeliveryRuleElementData extends RuleElementData { + public extended: number; + public reserved: number; + public minutes: number; + public constructor(sb: StreamBuffer) { + super(sb); + this.extended = sb.readUInt32(); + this.reserved = sb.readUInt32(); + this.minutes = sb.readUInt32(); + } } class FlatEntry { @@ -639,99 +736,90 @@ class RuleElement { re.id = sb.readUInt32(); switch (re.id) { // Mandatory rule elements - case 0x64: { - re.description = 'Unknown'; - re.data = new UnknownRuleElement0x64Data(sb); - break; - } - case 0x190: { - re.description = 'type of message to which this rule applies'; - re.data = new ApplyRuleElementData(sb); - break; - } - // Conditions - case 0xc8: { - re.description = 'where my name is in the To box'; - re.data = new SimpleRuleElementData(sb); - break; - } - case 0xc9: { - re.description = 'sent only to me'; - re.data = new SimpleRuleElementData(sb); - break; - } - case 0xca: { - re.description = 'where my name is not in the To box'; - re.data = new SimpleRuleElementData(sb); - break; - } - case 0xcb: { - re.description = 'from '; - re.data = new PeopleOrPublicGroupListRuleElementData(sb); - break; - } - case 0xcc: { - re.description = 'sent to '; - re.data = new PeopleOrPublicGroupListRuleElementData(sb); - break; - } - case 0xcd: { - re.description = 'with specific words in the subject'; - re.data = new StringsListRuleElementData(sb); - break; - } - case 0xce: { - re.description = 'with specific words in the body'; - re.data = new StringsListRuleElementData(sb); - break; - } - case 0xcf: { - re.description = 'with specific words in the subject or body'; - re.data = new StringsListRuleElementData(sb); - break; - } - case 0xd0: { - re.description = 'flagged for '; - re.data = new FlaggedForActionRuleElementData(sb); - break; - } - - case 0xe2: { - re.description = 'where my name is in the CC box'; - re.data = new SimpleRuleElementData(sb); - break; - } + case 0x64: re.description = 'Unknown'; re.data = new UnknownRuleElement0x64Data(sb); break; + case 0x190: re.description = 'type of message to which this rule applies'; re.data = new ApplyRuleElementData(sb); break; - case 0xe8: { - re.description = 'with specific words in the message header'; - re.data = new StringsListRuleElementData(sb); - break; - } - - case 0xf6: { - re.description = 'assigned to any category'; - re.data = new SimpleRuleElementData(sb); - break; - } - - case 0x12c: { - re.description = 'move it to the specified folder'; - re.data = new MoveToFolderRuleElementData(sb); - break; - } - case 0x142: { - re.description = 'stop processing more rules'; - re.data = new SimpleRuleElementData(sb); - break; - } - case 0x152: { - re.description = 'clear message categories'; - re.data = new SimpleRuleElementData(sb); - break; - } + // Conditions + case 0xc8: re.description = 'where my name is in the To box'; re.data = new SimpleRuleElementData(sb); break; + case 0xc9: re.description = 'sent only to me'; re.data = new SimpleRuleElementData(sb); break; + case 0xca: re.description = 'where my name is not in the To box'; re.data = new SimpleRuleElementData(sb); break; + case 0xcb: re.description = 'from '; re.data = new PeopleOrPublicGroupListRuleElementData(sb); break; + case 0xcc: re.description = 'sent to '; re.data = new PeopleOrPublicGroupListRuleElementData(sb); break; + case 0xcd: re.description = 'with specific words in the subject'; re.data = new StringsListRuleElementData(sb); break; + case 0xce: re.description = 'with specific words in the body'; re.data = new StringsListRuleElementData(sb); break; + case 0xcf: re.description = 'with specific words in the subject or body'; re.data = new StringsListRuleElementData(sb); break; + case 0xd0: re.description = 'flagged for '; re.data = new FlaggedForActionRuleElementData(sb); break; + case 0xd2: re.description = 'marked as '; re.data = new ImportanceRuleElementData(sb); break; + case 0xd3: re.description = 'marked as '; re.data = new SensitivityRuleElementData(sb); break; + case 0xd7: re.description = 'assigned to category'; re.data = new CategoriesListRuleElementData(sb); break; + case 0xdc: re.description = 'which is an automatic reply'; re.data = new SimpleRuleElementData(sb); break; + case 0xde: re.description = 'which has an attachment'; re.data = new SimpleRuleElementData(sb); break; + case 0xe0: re.description = 'with a size in a specific range'; re.data = new SizeInSpecificRangeRuleElementData(sb); break; + case 0xe2: re.description = 'where my name is in the CC box'; re.data = new SimpleRuleElementData(sb); break; + case 0xe3: re.description = 'where my name is in the To or CC box'; re.data = new SimpleRuleElementData(sb); break; + case 0xe5: re.description = 'with specific words in the recipient\'s address'; re.data = new StringsListRuleElementData(sb); break; + case 0xe6: re.description = 'with specific words in the sender\'s address'; re.data = new StringsListRuleElementData(sb); break; + case 0xe8: re.description = 'with specific words in the message header'; re.data = new StringsListRuleElementData(sb); break; + case 0xee: re.description = 'through the specified account'; re.data = new PathRuleElementData(sb); break; + case 0xef: re.description = 'on this computer only'; re.data = new OnThisComputerOnlyRuleElementData(sb); break; + case 0xf1: re.description = 'which is a meeting invitation or update'; re.data = new SimpleRuleElementData(sb); break; + case 0xf5: re.description = 'from RSS feeds with specified text in the title'; re.data = new StringsListRuleElementData(sb); break; + case 0xf6: re.description = 'assigned to any category'; re.data = new SimpleRuleElementData(sb); break; + case 0xf7: re.description = 'from any RSS feed'; re.data = new SimpleRuleElementData(sb); break; + + // Actions + case 0x12c: re.description = 'move it to the specified folder'; re.data = new MoveToFolderRuleElementData(sb); break; + case 0x12d: re.description = 'delete it'; re.data = new SimpleRuleElementData(sb); break; + case 0x12e: re.description = 'forward it to '; re.data = new PeopleOrPublicGroupListRuleElementData(sb); break; + case 0x12f: re.description = 'reply using