@rapidmx/mapi-plugin 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +382 -0
- package/README.md +60 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js +297 -0
- package/dist/lib/BaseMapiEmsmdbRoute.js.map +1 -0
- package/dist/lib/BaseMapiNspiRoute.js +114 -0
- package/dist/lib/BaseMapiNspiRoute.js.map +1 -0
- package/dist/lib/MapiSessionManager.js +119 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RopDispatcher.js +34 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +314 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +213 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +92 -0
- package/dist/lib/codec/GlobalObjectId.js.map +1 -0
- package/dist/lib/codec/MapiGuid.js +41 -0
- package/dist/lib/codec/MapiGuid.js.map +1 -0
- package/dist/lib/codec/MapiTimeZone.js +93 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +214 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +26 -0
- package/dist/lib/codec/RopBuffer.js.map +1 -0
- package/dist/lib/codec/TypedString.js +54 -0
- package/dist/lib/codec/TypedString.js.map +1 -0
- package/dist/lib/index.js +99 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js +84 -0
- package/dist/lib/mongo/MapiEmsmdbRouteMongo.js.map +1 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js +33 -0
- package/dist/lib/mongo/MapiNspiRouteMongo.js.map +1 -0
- package/dist/lib/mongo/index.js +7 -0
- package/dist/lib/mongo/index.js.map +1 -0
- package/dist/lib/mongo.js +6 -0
- package/dist/lib/mongo.js.map +1 -0
- package/dist/lib/nspi/NspiBindHandler.js +54 -0
- package/dist/lib/nspi/NspiBindHandler.js.map +1 -0
- package/dist/lib/nspi/NspiCodec.js +106 -0
- package/dist/lib/nspi/NspiCodec.js.map +1 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js +142 -0
- package/dist/lib/nspi/NspiGetMatchesHandler.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +27 -0
- package/dist/lib/rop/CalendarEventTarget.js.map +1 -0
- package/dist/lib/rop/CalendarNamedProperties.js +57 -0
- package/dist/lib/rop/CalendarNamedProperties.js.map +1 -0
- package/dist/lib/rop/ContactTarget.js +23 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +98 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +111 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +95 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +42 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +44 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +66 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +313 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +53 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +133 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +70 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js +61 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +75 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +76 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +40 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +55 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js +92 -0
- package/dist/lib/rop/RopGetPropertyIdsFromNamesHandler.js.map +1 -0
- package/dist/lib/rop/RopHandler.js +2 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +132 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +46 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +81 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +81 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +91 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +55 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +20 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +210 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +52 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +162 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +314 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +45 -0
- package/dist/lib/rop/RopWriteStreamHandler.js.map +1 -0
- package/dist/lib/rop/TaskNamedProperties.js +29 -0
- package/dist/lib/rop/TaskNamedProperties.js.map +1 -0
- package/dist/lib/rop/TaskTarget.js +19 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +84 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js.map +1 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js +33 -0
- package/dist/lib/sql/MapiNspiRouteSQL.js.map +1 -0
- package/dist/lib/sql/index.js +7 -0
- package/dist/lib/sql/index.js.map +1 -0
- package/dist/lib/sql.js +6 -0
- package/dist/lib/sql.js.map +1 -0
- package/dist/types/BaseMapiEmsmdbRoute.d.ts +82 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +130 -0
- package/dist/types/RopDispatcher.d.ts +17 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +78 -0
- package/dist/types/codec/GlobalObjectId.d.ts +11 -0
- package/dist/types/codec/MapiGuid.d.ts +6 -0
- package/dist/types/codec/MapiTimeZone.d.ts +7 -0
- package/dist/types/codec/PropertyValue.d.ts +77 -0
- package/dist/types/codec/RopBuffer.d.ts +25 -0
- package/dist/types/codec/TypedString.d.ts +17 -0
- package/dist/types/index.d.ts +94 -0
- package/dist/types/mongo/MapiEmsmdbRouteMongo.d.ts +19 -0
- package/dist/types/mongo/MapiNspiRouteMongo.d.ts +13 -0
- package/dist/types/mongo/index.d.ts +2 -0
- package/dist/types/mongo.d.ts +1 -0
- package/dist/types/nspi/NspiBindHandler.d.ts +22 -0
- package/dist/types/nspi/NspiCodec.d.ts +59 -0
- package/dist/types/nspi/NspiGetMatchesHandler.d.ts +24 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +29 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/FastTransferStream.d.ts +18 -0
- package/dist/types/rop/FolderTarget.d.ts +63 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +36 -0
- package/dist/types/rop/MessageBodyStream.d.ts +26 -0
- package/dist/types/rop/MessageTarget.d.ts +38 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +46 -0
- package/dist/types/rop/PropertyResolvers.d.ts +60 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +23 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +38 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +31 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +24 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +23 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +31 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +17 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +23 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +67 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +19 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +27 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +27 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +36 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +18 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +14 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +44 -0
- package/dist/types/rop/RopSetColumnsHandler.d.ts +21 -0
- package/dist/types/rop/RopSetPropertiesHandler.d.ts +38 -0
- package/dist/types/rop/RopSubmitMessageHandler.d.ts +70 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +21 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +19 -0
- package/dist/types/sql/MapiNspiRouteSQL.d.ts +13 -0
- package/dist/types/sql/index.d.ts +2 -0
- package/dist/types/sql.d.ts +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferWriter } from "./BufferCursor.js";
|
|
6
|
+
import { RecurrenceFrequency } from "@rapidmx/restapi";
|
|
7
|
+
/**
|
|
8
|
+
* Encodes/decodes `CalendarEvent.recurrenceRule` to/from the binary blob carried by `PidLidAppointmentRecur`
|
|
9
|
+
* (`[MS-OXOCAL]` §2.2.1.44: the outer `AppointmentRecurrencePattern` structure, §2.2.1.44.5, wrapping an inner
|
|
10
|
+
* `RecurrencePattern`, §2.2.1.44.1). Every field layout/size below was confirmed against those spec pages
|
|
11
|
+
* directly, not assumed.
|
|
12
|
+
*
|
|
13
|
+
* **Pragmatic scope, matching the same limits `CalendarSyncAdapter` (EAS) already carries**:
|
|
14
|
+
* - Only `RecurFrequency` Daily/Weekly/Monthly/Yearly and `PatternType` Day/Week/Month are produced or
|
|
15
|
+
* accepted - `MonthNth`/`MonthEnd`/Hijri patterns have no representation in `RecurrenceRule` (no ordinal-
|
|
16
|
+
* weekday field) and decoding one throws a clear, documented error rather than silently guessing.
|
|
17
|
+
* - `DeletedInstanceCount`/`ModifiedInstanceCount` (recurrence exceptions) and the outer structure's
|
|
18
|
+
* `ExceptionCount` are always written as `0`; decoding a nonzero value throws, since `RecurrenceRule` has no
|
|
19
|
+
* field to hold per-instance exceptions.
|
|
20
|
+
* - A `YEARLY` rule with `interval > 1` ("every N years", N>1) is encoded as `RecurFrequency=Monthly` with
|
|
21
|
+
* `Period=12*N`, per the spec's own stated rule that "a yearly recurrence pattern is just a monthly pattern
|
|
22
|
+
* that occurs every 12 months" (a plain `RecurFrequency=Yearly` value's `Period` MUST be exactly 12).
|
|
23
|
+
* **This is a one-way, documented round-trip asymmetry**: decoding that same blob back yields
|
|
24
|
+
* `{freq: MONTHLY, interval: 12*N}`, not the original `{freq: YEARLY, interval: N}` - the two are
|
|
25
|
+
* indistinguishable on the wire once encoded, the same ambiguity a real Exchange server itself has no way
|
|
26
|
+
* around either.
|
|
27
|
+
* - `StartTimeOffset`/`EndTimeOffset` (the outer structure) are derived from `startDate`/`endDate`'s own
|
|
28
|
+
* time-of-day only (`hours*60+minutes`), not carried through decode - the real start/end instant of any
|
|
29
|
+
* given occurrence comes from `PidLidAppointmentStartWhole`/`EndWhole`, not from this blob.
|
|
30
|
+
*
|
|
31
|
+
* @author Jean-Philippe Steinmetz
|
|
32
|
+
*/
|
|
33
|
+
/** `RecurFrequency` (`[MS-OXOCAL]` §2.2.1.44.1). */
|
|
34
|
+
var RecurFrequencyWire;
|
|
35
|
+
(function (RecurFrequencyWire) {
|
|
36
|
+
RecurFrequencyWire[RecurFrequencyWire["Daily"] = 8202] = "Daily";
|
|
37
|
+
RecurFrequencyWire[RecurFrequencyWire["Weekly"] = 8203] = "Weekly";
|
|
38
|
+
RecurFrequencyWire[RecurFrequencyWire["Monthly"] = 8204] = "Monthly";
|
|
39
|
+
RecurFrequencyWire[RecurFrequencyWire["Yearly"] = 8205] = "Yearly";
|
|
40
|
+
})(RecurFrequencyWire || (RecurFrequencyWire = {}));
|
|
41
|
+
/** `PatternType` (`[MS-OXOCAL]` §2.2.1.44.1) - only the 3 values this codec produces/accepts. */
|
|
42
|
+
var PatternTypeWire;
|
|
43
|
+
(function (PatternTypeWire) {
|
|
44
|
+
PatternTypeWire[PatternTypeWire["Day"] = 0] = "Day";
|
|
45
|
+
PatternTypeWire[PatternTypeWire["Week"] = 1] = "Week";
|
|
46
|
+
PatternTypeWire[PatternTypeWire["Month"] = 2] = "Month";
|
|
47
|
+
})(PatternTypeWire || (PatternTypeWire = {}));
|
|
48
|
+
/** `EndType` (`[MS-OXOCAL]` §2.2.1.44.1). */
|
|
49
|
+
var EndTypeWire;
|
|
50
|
+
(function (EndTypeWire) {
|
|
51
|
+
EndTypeWire[EndTypeWire["AfterDate"] = 8225] = "AfterDate";
|
|
52
|
+
EndTypeWire[EndTypeWire["AfterN"] = 8226] = "AfterN";
|
|
53
|
+
EndTypeWire[EndTypeWire["Never"] = 8227] = "Never";
|
|
54
|
+
})(EndTypeWire || (EndTypeWire = {}));
|
|
55
|
+
/** Sentinel `EndDate` value (`[MS-OXOCAL]` §2.2.1.44.1) meaning "never-ending". */
|
|
56
|
+
const NEVER_END_DATE = 0x5ae980df;
|
|
57
|
+
const MINUTES_PER_DAY = 1440;
|
|
58
|
+
const MINUTES_PER_WEEK = 10080;
|
|
59
|
+
/** `RecurrencePattern.CalendarType` `Default` value (Gregorian) - the only calendar type this codec supports. */
|
|
60
|
+
const CALENDAR_TYPE_DEFAULT = 0x0000;
|
|
61
|
+
/** `RecurrencePattern.FirstDOW` `Sunday` value - this codec always encodes with a Sunday week start, since
|
|
62
|
+
* `RecurrenceRule` has no field of its own to source a different one from. */
|
|
63
|
+
const FIRST_DOW_SUNDAY = 0x00000000;
|
|
64
|
+
/** `RecurrencePattern`'s `DayOfWeekMask` bits (`[MS-OXOCAL]` §2.2.1.44.1) - confirmed identical to
|
|
65
|
+
* `CalendarSyncAdapter`'s own `DAY_OF_WEEK_BITS` table for MS-ASCAL's `DayOfWeek` field, reused here under the
|
|
66
|
+
* same RFC 5545 two-letter day codes `RecurrenceRule.byDay` uses. */
|
|
67
|
+
const DAY_OF_WEEK_BITS = { SU: 1, MO: 2, TU: 4, WE: 8, TH: 16, FR: 32, SA: 64 };
|
|
68
|
+
const DAY_OF_WEEK_CODE_BY_BIT = [
|
|
69
|
+
["SU", 1],
|
|
70
|
+
["MO", 2],
|
|
71
|
+
["TU", 4],
|
|
72
|
+
["WE", 8],
|
|
73
|
+
["TH", 16],
|
|
74
|
+
["FR", 32],
|
|
75
|
+
["SA", 64],
|
|
76
|
+
];
|
|
77
|
+
/** Minutes-since-1601-01-01T00:00:00Z epoch, as a JS `Date`-compatible millisecond timestamp - every date field
|
|
78
|
+
* in `RecurrencePattern` uses this epoch/unit, distinct from `PropertyValue.ts`'s `FILETIME` epoch (which is
|
|
79
|
+
* also 1601-based, but in 100-ns units, not minutes). */
|
|
80
|
+
const EPOCH_1601_MS = Date.UTC(1601, 0, 1);
|
|
81
|
+
function dateToMinutesSince1601(date) {
|
|
82
|
+
return Math.round((date.getTime() - EPOCH_1601_MS) / 60000);
|
|
83
|
+
}
|
|
84
|
+
function minutesSince1601ToDate(minutes) {
|
|
85
|
+
return new Date(EPOCH_1601_MS + minutes * 60000);
|
|
86
|
+
}
|
|
87
|
+
function utcMidnight(date) {
|
|
88
|
+
return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
|
|
89
|
+
}
|
|
90
|
+
/** Calendar months elapsed between 1601-01-01 and the first day of the month containing `date`. */
|
|
91
|
+
function monthsElapsedSince1601(date) {
|
|
92
|
+
return (date.getUTCFullYear() - 1601) * 12 + date.getUTCMonth();
|
|
93
|
+
}
|
|
94
|
+
function dayOfWeekMaskFromByDay(byDay, fallbackDate) {
|
|
95
|
+
if (byDay && byDay.length > 0) {
|
|
96
|
+
return byDay.reduce((mask, day) => mask | (DAY_OF_WEEK_BITS[day] ?? 0), 0);
|
|
97
|
+
}
|
|
98
|
+
return DAY_OF_WEEK_CODE_BY_BIT[fallbackDate.getUTCDay()][1];
|
|
99
|
+
}
|
|
100
|
+
function byDayFromMask(mask) {
|
|
101
|
+
return DAY_OF_WEEK_CODE_BY_BIT.filter(([, bit]) => (mask & bit) !== 0).map(([code]) => code);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Encodes `rule` (plus the occurrence's own `startDate`/`endDate`, needed for phase/time-of-day derivation)
|
|
105
|
+
* into an `AppointmentRecurrencePattern` blob, ready to be written as the raw content of a `PtypBinary`
|
|
106
|
+
* `PidLidAppointmentRecur` property value.
|
|
107
|
+
*/
|
|
108
|
+
export function encodeAppointmentRecurrence(rule, startDate, endDate) {
|
|
109
|
+
let recurFrequency;
|
|
110
|
+
let patternType;
|
|
111
|
+
let period;
|
|
112
|
+
let patternSpecific;
|
|
113
|
+
let firstDateTime;
|
|
114
|
+
switch (rule.freq) {
|
|
115
|
+
case RecurrenceFrequency.DAILY: {
|
|
116
|
+
recurFrequency = RecurFrequencyWire.Daily;
|
|
117
|
+
patternType = PatternTypeWire.Day;
|
|
118
|
+
period = rule.interval * MINUTES_PER_DAY;
|
|
119
|
+
patternSpecific = Buffer.alloc(0);
|
|
120
|
+
firstDateTime = dateToMinutesSince1601(utcMidnight(startDate)) % period;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case RecurrenceFrequency.WEEKLY: {
|
|
124
|
+
recurFrequency = RecurFrequencyWire.Weekly;
|
|
125
|
+
patternType = PatternTypeWire.Week;
|
|
126
|
+
period = rule.interval;
|
|
127
|
+
const mask = dayOfWeekMaskFromByDay(rule.byDay, startDate);
|
|
128
|
+
patternSpecific = new BufferWriter().writeUInt32LE(mask).toBuffer();
|
|
129
|
+
const midnight = utcMidnight(startDate);
|
|
130
|
+
const weekStart = new Date(midnight.getTime() - midnight.getUTCDay() * MINUTES_PER_DAY * 60000);
|
|
131
|
+
firstDateTime = dateToMinutesSince1601(weekStart) % (period * MINUTES_PER_WEEK);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case RecurrenceFrequency.MONTHLY: {
|
|
135
|
+
recurFrequency = RecurFrequencyWire.Monthly;
|
|
136
|
+
patternType = PatternTypeWire.Month;
|
|
137
|
+
period = rule.interval;
|
|
138
|
+
const day = rule.byMonthDay?.[0] ?? startDate.getUTCDate();
|
|
139
|
+
patternSpecific = new BufferWriter().writeUInt32LE(day).toBuffer();
|
|
140
|
+
const monthsElapsed = monthsElapsedSince1601(startDate);
|
|
141
|
+
const refMonthIndex = monthsElapsed % period;
|
|
142
|
+
firstDateTime = dateToMinutesSince1601(new Date(Date.UTC(1601, refMonthIndex, 1)));
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case RecurrenceFrequency.YEARLY: {
|
|
146
|
+
patternType = PatternTypeWire.Month;
|
|
147
|
+
if (rule.interval <= 1) {
|
|
148
|
+
recurFrequency = RecurFrequencyWire.Yearly;
|
|
149
|
+
period = 12;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
recurFrequency = RecurFrequencyWire.Monthly;
|
|
153
|
+
period = 12 * rule.interval;
|
|
154
|
+
}
|
|
155
|
+
const day = rule.byMonthDay?.[0] ?? startDate.getUTCDate();
|
|
156
|
+
patternSpecific = new BufferWriter().writeUInt32LE(day).toBuffer();
|
|
157
|
+
const monthsElapsed = monthsElapsedSince1601(startDate);
|
|
158
|
+
const refMonthIndex = monthsElapsed % period;
|
|
159
|
+
firstDateTime = dateToMinutesSince1601(new Date(Date.UTC(1601, refMonthIndex, 1)));
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
default:
|
|
163
|
+
throw new Error(`AppointmentRecurrence: unsupported RecurrenceFrequency ${rule.freq}`);
|
|
164
|
+
}
|
|
165
|
+
let endType;
|
|
166
|
+
let occurrenceCount = 0;
|
|
167
|
+
let endDateMinutes;
|
|
168
|
+
if (rule.until) {
|
|
169
|
+
endType = EndTypeWire.AfterDate;
|
|
170
|
+
endDateMinutes = dateToMinutesSince1601(utcMidnight(rule.until));
|
|
171
|
+
}
|
|
172
|
+
else if (rule.count != null) {
|
|
173
|
+
endType = EndTypeWire.AfterN;
|
|
174
|
+
occurrenceCount = rule.count;
|
|
175
|
+
endDateMinutes = NEVER_END_DATE;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
endType = EndTypeWire.Never;
|
|
179
|
+
endDateMinutes = NEVER_END_DATE;
|
|
180
|
+
}
|
|
181
|
+
const writer = new BufferWriter();
|
|
182
|
+
// RecurrencePattern (MS-OXOCAL §2.2.1.44.1)
|
|
183
|
+
writer.writeUInt16LE(0x3004); // ReaderVersion
|
|
184
|
+
writer.writeUInt16LE(0x3004); // WriterVersion
|
|
185
|
+
writer.writeUInt16LE(recurFrequency);
|
|
186
|
+
writer.writeUInt16LE(patternType);
|
|
187
|
+
writer.writeUInt16LE(CALENDAR_TYPE_DEFAULT);
|
|
188
|
+
writer.writeUInt32LE(firstDateTime);
|
|
189
|
+
writer.writeUInt32LE(period);
|
|
190
|
+
writer.writeUInt32LE(0); // SlidingFlag
|
|
191
|
+
writer.writeBytes(patternSpecific);
|
|
192
|
+
writer.writeUInt32LE(endType);
|
|
193
|
+
writer.writeUInt32LE(occurrenceCount);
|
|
194
|
+
writer.writeUInt32LE(FIRST_DOW_SUNDAY);
|
|
195
|
+
writer.writeUInt32LE(0); // DeletedInstanceCount
|
|
196
|
+
writer.writeUInt32LE(0); // ModifiedInstanceCount
|
|
197
|
+
writer.writeUInt32LE(dateToMinutesSince1601(utcMidnight(startDate))); // StartDate
|
|
198
|
+
writer.writeUInt32LE(endDateMinutes); // EndDate
|
|
199
|
+
// AppointmentRecurrencePattern's own wrapper fields (MS-OXOCAL §2.2.1.44.5)
|
|
200
|
+
writer.writeUInt32LE(0x00003006); // ReaderVersion2
|
|
201
|
+
writer.writeUInt32LE(0x00003009); // WriterVersion2
|
|
202
|
+
writer.writeUInt32LE(startDate.getUTCHours() * 60 + startDate.getUTCMinutes()); // StartTimeOffset
|
|
203
|
+
writer.writeUInt32LE(endDate.getUTCHours() * 60 + endDate.getUTCMinutes()); // EndTimeOffset
|
|
204
|
+
writer.writeUInt16LE(0); // ExceptionCount
|
|
205
|
+
writer.writeUInt32LE(0); // ReservedBlock1Size
|
|
206
|
+
writer.writeUInt32LE(0); // ReservedBlock2Size
|
|
207
|
+
return writer.toBuffer();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Decodes an `AppointmentRecurrencePattern` blob (read from `reader`'s current position) back into a
|
|
211
|
+
* `RecurrenceRule`. Throws a clear error for any pattern this pragmatic subset can't represent (see this
|
|
212
|
+
* file's own doc comment) rather than silently dropping data.
|
|
213
|
+
*/
|
|
214
|
+
export function decodeAppointmentRecurrence(reader) {
|
|
215
|
+
reader.readUInt16LE(); // ReaderVersion
|
|
216
|
+
reader.readUInt16LE(); // WriterVersion
|
|
217
|
+
const recurFrequency = reader.readUInt16LE();
|
|
218
|
+
const patternType = reader.readUInt16LE();
|
|
219
|
+
reader.readUInt16LE(); // CalendarType
|
|
220
|
+
const firstDateTime = reader.readUInt32LE();
|
|
221
|
+
const period = reader.readUInt32LE();
|
|
222
|
+
reader.readUInt32LE(); // SlidingFlag
|
|
223
|
+
let freq;
|
|
224
|
+
let interval;
|
|
225
|
+
let byDay;
|
|
226
|
+
let byMonthDay;
|
|
227
|
+
let byMonth;
|
|
228
|
+
switch (recurFrequency) {
|
|
229
|
+
case RecurFrequencyWire.Daily:
|
|
230
|
+
if (patternType !== PatternTypeWire.Day) {
|
|
231
|
+
throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Daily`);
|
|
232
|
+
}
|
|
233
|
+
freq = RecurrenceFrequency.DAILY;
|
|
234
|
+
interval = period / MINUTES_PER_DAY;
|
|
235
|
+
break;
|
|
236
|
+
case RecurFrequencyWire.Weekly: {
|
|
237
|
+
if (patternType !== PatternTypeWire.Week) {
|
|
238
|
+
throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Weekly`);
|
|
239
|
+
}
|
|
240
|
+
const mask = reader.readUInt32LE();
|
|
241
|
+
freq = RecurrenceFrequency.WEEKLY;
|
|
242
|
+
interval = period;
|
|
243
|
+
byDay = byDayFromMask(mask);
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case RecurFrequencyWire.Monthly: {
|
|
247
|
+
if (patternType !== PatternTypeWire.Month) {
|
|
248
|
+
throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Monthly`);
|
|
249
|
+
}
|
|
250
|
+
const day = reader.readUInt32LE();
|
|
251
|
+
freq = RecurrenceFrequency.MONTHLY;
|
|
252
|
+
interval = period;
|
|
253
|
+
byMonthDay = [day];
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
case RecurFrequencyWire.Yearly: {
|
|
257
|
+
if (patternType !== PatternTypeWire.Month) {
|
|
258
|
+
throw new Error(`AppointmentRecurrence: unsupported PatternType 0x${patternType.toString(16)} for Yearly`);
|
|
259
|
+
}
|
|
260
|
+
const day = reader.readUInt32LE();
|
|
261
|
+
freq = RecurrenceFrequency.YEARLY;
|
|
262
|
+
interval = 1;
|
|
263
|
+
byMonthDay = [day];
|
|
264
|
+
byMonth = [minutesSince1601ToDate(firstDateTime).getUTCMonth() + 1];
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
default:
|
|
268
|
+
throw new Error(`AppointmentRecurrence: unsupported RecurFrequency 0x${recurFrequency.toString(16)}`);
|
|
269
|
+
}
|
|
270
|
+
const endType = reader.readUInt32LE();
|
|
271
|
+
const occurrenceCount = reader.readUInt32LE();
|
|
272
|
+
reader.readUInt32LE(); // FirstDOW
|
|
273
|
+
const deletedInstanceCount = reader.readUInt32LE();
|
|
274
|
+
if (deletedInstanceCount > 0) {
|
|
275
|
+
throw new Error("AppointmentRecurrence: recurrence exceptions (DeletedInstanceCount > 0) are not supported");
|
|
276
|
+
}
|
|
277
|
+
const modifiedInstanceCount = reader.readUInt32LE();
|
|
278
|
+
if (modifiedInstanceCount > 0) {
|
|
279
|
+
throw new Error("AppointmentRecurrence: recurrence exceptions (ModifiedInstanceCount > 0) are not supported");
|
|
280
|
+
}
|
|
281
|
+
reader.readUInt32LE(); // StartDate
|
|
282
|
+
const endDateMinutes = reader.readUInt32LE();
|
|
283
|
+
let until;
|
|
284
|
+
let count;
|
|
285
|
+
if (endType === EndTypeWire.AfterDate) {
|
|
286
|
+
until = minutesSince1601ToDate(endDateMinutes);
|
|
287
|
+
}
|
|
288
|
+
else if (endType === EndTypeWire.AfterN) {
|
|
289
|
+
count = occurrenceCount;
|
|
290
|
+
}
|
|
291
|
+
reader.readUInt32LE(); // ReaderVersion2
|
|
292
|
+
reader.readUInt32LE(); // WriterVersion2
|
|
293
|
+
reader.readUInt32LE(); // StartTimeOffset
|
|
294
|
+
reader.readUInt32LE(); // EndTimeOffset
|
|
295
|
+
const exceptionCount = reader.readUInt16LE();
|
|
296
|
+
if (exceptionCount > 0) {
|
|
297
|
+
throw new Error("AppointmentRecurrence: exceptions (ExceptionCount > 0) are not supported");
|
|
298
|
+
}
|
|
299
|
+
const reservedBlock1Size = reader.readUInt32LE();
|
|
300
|
+
reader.readBytes(reservedBlock1Size);
|
|
301
|
+
const reservedBlock2Size = reader.readUInt32LE();
|
|
302
|
+
reader.readBytes(reservedBlock2Size);
|
|
303
|
+
return {
|
|
304
|
+
freq,
|
|
305
|
+
interval,
|
|
306
|
+
...(byDay ? { byDay } : {}),
|
|
307
|
+
...(byMonthDay ? { byMonthDay } : {}),
|
|
308
|
+
...(byMonth ? { byMonth } : {}),
|
|
309
|
+
...(until ? { until } : {}),
|
|
310
|
+
...(count != null ? { count } : {}),
|
|
311
|
+
exceptions: [],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=AppointmentRecurrence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AppointmentRecurrence.js","sourceRoot":"","sources":["../../../src/codec/AppointmentRecurrence.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* A small positional cursor over a `Buffer`, shared by every MAPI codec in this package (`MapiGuid`,
|
|
7
|
+
* `PropertyValue`, `RopBuffer`, and every individual ROP handler's own request/response encoding). Unlike
|
|
8
|
+
* WBXML's self-describing tag stream, MAPI's wire structures are fixed/variable-length binary records with no
|
|
9
|
+
* generic "read until end tag" mechanism, so every codec needs the same little-endian primitive reads/writes
|
|
10
|
+
* with automatic offset advancement - reimplementing that per file would be pure repetition across the dozens
|
|
11
|
+
* of ROP handlers this phase's plan calls for, unlike EAS's WBXML codec, which only needed one such reader.
|
|
12
|
+
*
|
|
13
|
+
* Underlying `Buffer` methods already bounds-check and throw `RangeError` on overflow/truncation for every
|
|
14
|
+
* fixed-width primitive read - a malformed or truncated ROP buffer legitimately failing loudly is the correct
|
|
15
|
+
* behavior, not something to swallow here. `readBytes`/`readNullTerminatedUtf16LE`/`readNullTerminatedString8`
|
|
16
|
+
* are the three exceptions: `Buffer.subarray` silently clamps instead of throwing, and a null-terminated scan
|
|
17
|
+
* has no fixed width to bounds-check against `Buffer` methods at all, so those three enforce the same
|
|
18
|
+
* "malformed/truncated input throws" contract explicitly below instead of relying on it happening for free.
|
|
19
|
+
*
|
|
20
|
+
* @author Jean-Philippe Steinmetz
|
|
21
|
+
*/
|
|
22
|
+
export class BufferReader {
|
|
23
|
+
constructor(buffer, offset = 0) {
|
|
24
|
+
this.buffer = buffer;
|
|
25
|
+
this.offset = offset;
|
|
26
|
+
}
|
|
27
|
+
get position() {
|
|
28
|
+
return this.offset;
|
|
29
|
+
}
|
|
30
|
+
get remaining() {
|
|
31
|
+
return this.buffer.length - this.offset;
|
|
32
|
+
}
|
|
33
|
+
hasMore() {
|
|
34
|
+
return this.offset < this.buffer.length;
|
|
35
|
+
}
|
|
36
|
+
readUInt8() {
|
|
37
|
+
const value = this.buffer.readUInt8(this.offset);
|
|
38
|
+
this.offset += 1;
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
readInt16LE() {
|
|
42
|
+
const value = this.buffer.readInt16LE(this.offset);
|
|
43
|
+
this.offset += 2;
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
readUInt16LE() {
|
|
47
|
+
const value = this.buffer.readUInt16LE(this.offset);
|
|
48
|
+
this.offset += 2;
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
readInt32LE() {
|
|
52
|
+
const value = this.buffer.readInt32LE(this.offset);
|
|
53
|
+
this.offset += 4;
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
readUInt32LE() {
|
|
57
|
+
const value = this.buffer.readUInt32LE(this.offset);
|
|
58
|
+
this.offset += 4;
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
readFloatLE() {
|
|
62
|
+
const value = this.buffer.readFloatLE(this.offset);
|
|
63
|
+
this.offset += 4;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
readDoubleLE() {
|
|
67
|
+
const value = this.buffer.readDoubleLE(this.offset);
|
|
68
|
+
this.offset += 8;
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
readBigInt64LE() {
|
|
72
|
+
const value = this.buffer.readBigInt64LE(this.offset);
|
|
73
|
+
this.offset += 8;
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
readBigUInt64LE() {
|
|
77
|
+
const value = this.buffer.readBigUInt64LE(this.offset);
|
|
78
|
+
this.offset += 8;
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
/** Unlike every fixed-width `readXxxLE` above, `Buffer.prototype.subarray` never throws - an out-of-range
|
|
82
|
+
* `length` (negative, or reaching past the buffer's end) silently clamps instead, which would otherwise let
|
|
83
|
+
* a malformed/truncated ROP buffer produce garbage-but-not-erroring reads (a negative `length` even rewinds
|
|
84
|
+
* `offset` backwards) instead of failing loudly the way this class's own doc comment promises. Explicitly
|
|
85
|
+
* bounds-checked here for that reason. */
|
|
86
|
+
readBytes(length) {
|
|
87
|
+
if (length < 0 || this.offset + length > this.buffer.length) {
|
|
88
|
+
throw new RangeError(`BufferReader.readBytes(${length}): out of range at offset ${this.offset} (buffer length ${this.buffer.length}).`);
|
|
89
|
+
}
|
|
90
|
+
const value = this.buffer.subarray(this.offset, this.offset + length);
|
|
91
|
+
this.offset += length;
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
/** Reads a UTF-16LE string up to (and consuming) its terminating `0x0000` code unit - `PtypString`'s wire
|
|
95
|
+
* encoding. Scans on 2-byte boundaries, since a null code unit's low/high byte pair can't be mistaken for
|
|
96
|
+
* one half of a non-null UTF-16 code unit at an odd offset the way a naive single-byte scan could.
|
|
97
|
+
*
|
|
98
|
+
* Throws if no terminator is found before running out of buffer, rather than silently returning whatever
|
|
99
|
+
* content preceded the truncation and leaving `offset` past the buffer's actual end - a truncated/malformed
|
|
100
|
+
* string is exactly the kind of input this class's own doc comment says should fail loudly. This also
|
|
101
|
+
* closes a real DoS: `PropertyValue.ts`'s `readCountedArray` loops a client-controlled element count with
|
|
102
|
+
* no independent cap, trusting each element's own reader to terminate the loop early against a too-small
|
|
103
|
+
* buffer the way every fixed-width reader already does - without this, `PtypMultipleString`'s
|
|
104
|
+
* `readNullTerminatedUtf16LE` calls kept silently returning `""` past the buffer's end forever instead. */
|
|
105
|
+
readNullTerminatedUtf16LE() {
|
|
106
|
+
let end = this.offset;
|
|
107
|
+
while (end + 1 < this.buffer.length && !(this.buffer[end] === 0 && this.buffer[end + 1] === 0)) {
|
|
108
|
+
end += 2;
|
|
109
|
+
}
|
|
110
|
+
if (end + 1 >= this.buffer.length) {
|
|
111
|
+
throw new RangeError("BufferReader.readNullTerminatedUtf16LE(): no null terminator found before the end of the buffer.");
|
|
112
|
+
}
|
|
113
|
+
const value = this.buffer.toString("utf16le", this.offset, end);
|
|
114
|
+
this.offset = end + 2;
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
/** Reads a single-byte-terminated 8-bit string (`PtypString8`'s wire encoding). Multibyte string content
|
|
118
|
+
* itself is treated as UTF-8, a pragmatic choice documented in `PropertyValue.ts` rather than the
|
|
119
|
+
* "externally specified encoding" the spec leaves open-ended.
|
|
120
|
+
*
|
|
121
|
+
* Throws if no terminator is found before running out of buffer - see `readNullTerminatedUtf16LE`'s own
|
|
122
|
+
* doc comment for why (identical reasoning, including the `readCountedArray` DoS this closes). */
|
|
123
|
+
readNullTerminatedString8() {
|
|
124
|
+
let end = this.offset;
|
|
125
|
+
while (end < this.buffer.length && this.buffer[end] !== 0) {
|
|
126
|
+
end += 1;
|
|
127
|
+
}
|
|
128
|
+
if (end >= this.buffer.length) {
|
|
129
|
+
throw new RangeError("BufferReader.readNullTerminatedString8(): no null terminator found before the end of the buffer.");
|
|
130
|
+
}
|
|
131
|
+
const value = this.buffer.toString("utf-8", this.offset, end);
|
|
132
|
+
this.offset = end + 1;
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** The write-side counterpart to `BufferReader`, accumulating chunks and concatenating once via `toBuffer()` -
|
|
137
|
+
* the same "array of pieces, `Buffer.concat` once at the end" idiom `WbxmlEncoder` already uses for its own
|
|
138
|
+
* byte-array accumulator, adapted for multi-byte little-endian fields instead of single bytes. */
|
|
139
|
+
export class BufferWriter {
|
|
140
|
+
constructor() {
|
|
141
|
+
this.chunks = [];
|
|
142
|
+
}
|
|
143
|
+
writeUInt8(value) {
|
|
144
|
+
const buf = Buffer.alloc(1);
|
|
145
|
+
buf.writeUInt8(value, 0);
|
|
146
|
+
this.chunks.push(buf);
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
writeInt16LE(value) {
|
|
150
|
+
const buf = Buffer.alloc(2);
|
|
151
|
+
buf.writeInt16LE(value, 0);
|
|
152
|
+
this.chunks.push(buf);
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
writeUInt16LE(value) {
|
|
156
|
+
const buf = Buffer.alloc(2);
|
|
157
|
+
buf.writeUInt16LE(value, 0);
|
|
158
|
+
this.chunks.push(buf);
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
writeInt32LE(value) {
|
|
162
|
+
const buf = Buffer.alloc(4);
|
|
163
|
+
buf.writeInt32LE(value, 0);
|
|
164
|
+
this.chunks.push(buf);
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
writeUInt32LE(value) {
|
|
168
|
+
const buf = Buffer.alloc(4);
|
|
169
|
+
buf.writeUInt32LE(value, 0);
|
|
170
|
+
this.chunks.push(buf);
|
|
171
|
+
return this;
|
|
172
|
+
}
|
|
173
|
+
writeFloatLE(value) {
|
|
174
|
+
const buf = Buffer.alloc(4);
|
|
175
|
+
buf.writeFloatLE(value, 0);
|
|
176
|
+
this.chunks.push(buf);
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
writeDoubleLE(value) {
|
|
180
|
+
const buf = Buffer.alloc(8);
|
|
181
|
+
buf.writeDoubleLE(value, 0);
|
|
182
|
+
this.chunks.push(buf);
|
|
183
|
+
return this;
|
|
184
|
+
}
|
|
185
|
+
writeBigInt64LE(value) {
|
|
186
|
+
const buf = Buffer.alloc(8);
|
|
187
|
+
buf.writeBigInt64LE(value, 0);
|
|
188
|
+
this.chunks.push(buf);
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
writeBigUInt64LE(value) {
|
|
192
|
+
const buf = Buffer.alloc(8);
|
|
193
|
+
buf.writeBigUInt64LE(value, 0);
|
|
194
|
+
this.chunks.push(buf);
|
|
195
|
+
return this;
|
|
196
|
+
}
|
|
197
|
+
writeBytes(value) {
|
|
198
|
+
this.chunks.push(value);
|
|
199
|
+
return this;
|
|
200
|
+
}
|
|
201
|
+
writeNullTerminatedUtf16LE(value) {
|
|
202
|
+
this.chunks.push(Buffer.from(value, "utf16le"), Buffer.from([0x00, 0x00]));
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
writeNullTerminatedString8(value) {
|
|
206
|
+
this.chunks.push(Buffer.from(value, "utf-8"), Buffer.from([0x00]));
|
|
207
|
+
return this;
|
|
208
|
+
}
|
|
209
|
+
toBuffer() {
|
|
210
|
+
return Buffer.concat(this.chunks);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=BufferCursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BufferCursor.js","sourceRoot":"","sources":["../../../src/codec/BufferCursor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
import { BufferWriter } from "./BufferCursor.js";
|
|
6
|
+
import { dateToFiletime } from "./PropertyValue.js";
|
|
7
|
+
/**
|
|
8
|
+
* Encodes/decodes `CalendarEvent.icalUid` to/from the `GlobalObjectId` BLOB carried by `PidLidGlobalObjectId`
|
|
9
|
+
* (`[MS-OXOCAL]` §2.2.1.27, confirmed field-by-field this session): `ByteArrayID(16, a fixed spec constant)
|
|
10
|
+
* +YH(1)+YL(1)+M(1)+D(1)+CreationTime(8,FILETIME)+X(8,reserved,zero)+Size(4)+Data(variable, Size bytes)` - 40
|
|
11
|
+
* fixed header bytes followed by `Data`.
|
|
12
|
+
*
|
|
13
|
+
* **This server never actually embeds a `GlobalObjectId` in the invites it sends** - `RopSubmitMessageHandler
|
|
14
|
+
* .submitAppointment()`'s invite is a plain RFC 5546 `METHOD:REQUEST` iCalendar attachment relayed over SMTP,
|
|
15
|
+
* not a native MAPI Store object a real client reads `PidLidGlobalObjectId` off of directly; that iCalendar
|
|
16
|
+
* only ever carries a plain `UID:` line. A real Outlook client receiving it therefore always *synthesizes* its
|
|
17
|
+
* own `PidLidGlobalObjectId` from that bare iCalendar UID, using the `VCALID` form both `[MS-ASEMAIL]` §2.2.2.37
|
|
18
|
+
* and the equivalent `[MS-OXCICAL]` UID-conversion algorithm document: `Data` = the 8-byte ASCII marker
|
|
19
|
+
* `"vCal-Uid"`, then a 4-byte version (`01 00 00 00`), then the UID's own bytes, then one trailing `NUL` -
|
|
20
|
+
* **not** the bare UID bytes this codec originally assumed. `decodeGlobalObjectId` recognizes and unwraps this
|
|
21
|
+
* form (falling back to treating `Data` as bare bytes only when the `vCal-Uid` marker isn't present, i.e. an
|
|
22
|
+
* `OutlookID` - a native-Exchange-generated `GlobalObjectId` this server's own SMTP-based invite flow never
|
|
23
|
+
* produces, but decoded for completeness). `encodeGlobalObjectId` now builds the same `VCALID` shape, both so
|
|
24
|
+
* a round trip through this codec models the real wire form and because `MeetingMessageClassHandler.test.ts`'s
|
|
25
|
+
* own test doubles need a realistic fixture to encode.
|
|
26
|
+
*
|
|
27
|
+
* **Pragmatic scope**: `YH`/`YL`/`M`/`D` (the `PidLidExceptionReplaceTime` fields, used only when a
|
|
28
|
+
* `GlobalObjectId` identifies a single modified occurrence of a recurring series) are always encoded as `0`
|
|
29
|
+
* ("not an exception") and not decoded - recurrence exceptions are a documented gap elsewhere in this pragmatic
|
|
30
|
+
* subset (see `AppointmentRecurrence.ts`'s own doc comment), so there is nothing to source a real value from.
|
|
31
|
+
* `PidLidCleanGlobalObjectId` (the sibling property real Outlook also sets, identical structure but with
|
|
32
|
+
* `YH`/`YL`/`M`/`D` always zeroed) is therefore byte-identical to this codec's own output and needs no separate
|
|
33
|
+
* implementation - the same value serves both properties.
|
|
34
|
+
*
|
|
35
|
+
* @author Jean-Philippe Steinmetz
|
|
36
|
+
*/
|
|
37
|
+
/** `ByteArrayID` (`[MS-OXOCAL]` §2.2.1.27): a fixed 16-byte constant identifying this BLOB as a `GlobalObjectId`
|
|
38
|
+
* - the spec's own exact required byte sequence, not invented. */
|
|
39
|
+
const BYTE_ARRAY_ID = Buffer.from([0x04, 0x00, 0x00, 0x00, 0x82, 0x00, 0xe0, 0x00, 0x74, 0xc5, 0xb7, 0x10, 0x1a, 0x82, 0xe0, 0x08]);
|
|
40
|
+
/** `VCALID`'s own fixed 8-byte ASCII marker (`[MS-ASEMAIL]` §2.2.2.37's `VCALSTRING`) identifying `Data` as a
|
|
41
|
+
* `"vCal-Uid"`-wrapped UID rather than a native `OutlookID`'s raw bytes. */
|
|
42
|
+
const VCAL_MARKER = Buffer.from("vCal-Uid", "ascii");
|
|
43
|
+
/** `VCALID`'s own fixed 4-byte `VERSION` field - always this exact literal byte sequence, not a real version
|
|
44
|
+
* negotiated at runtime. */
|
|
45
|
+
const VCAL_VERSION = Buffer.from([0x01, 0x00, 0x00, 0x00]);
|
|
46
|
+
/** A `VCALID`'s minimum possible `Data` length: `VCAL_MARKER` (8) + `VCAL_VERSION` (4) + a zero-length UID (0)
|
|
47
|
+
* + the trailing `NUL` (1). */
|
|
48
|
+
const VCAL_MIN_DATA_LENGTH = VCAL_MARKER.length + VCAL_VERSION.length + 1;
|
|
49
|
+
/** Encodes `icalUid` into a `GlobalObjectId` BLOB in the `VCALID` form a real client synthesizes from a plain
|
|
50
|
+
* iCalendar UID (see this file's own doc comment for why that's the form that matters here), using `at` as the
|
|
51
|
+
* `CreationTime`. */
|
|
52
|
+
export function encodeGlobalObjectId(icalUid, at) {
|
|
53
|
+
const data = Buffer.concat([VCAL_MARKER, VCAL_VERSION, Buffer.from(icalUid, "utf-8"), Buffer.from([0x00])]);
|
|
54
|
+
const writer = new BufferWriter();
|
|
55
|
+
writer.writeBytes(BYTE_ARRAY_ID);
|
|
56
|
+
writer.writeUInt8(0); // YH - not an exception, see class doc comment
|
|
57
|
+
writer.writeUInt8(0); // YL
|
|
58
|
+
writer.writeUInt8(0); // M
|
|
59
|
+
writer.writeUInt8(0); // D
|
|
60
|
+
writer.writeBigUInt64LE(dateToFiletime(at)); // CreationTime
|
|
61
|
+
writer.writeBytes(Buffer.alloc(8)); // X - reserved, MUST be zero
|
|
62
|
+
writer.writeUInt32LE(data.length); // Size
|
|
63
|
+
writer.writeBytes(data);
|
|
64
|
+
return writer.toBuffer();
|
|
65
|
+
}
|
|
66
|
+
/** Decodes a `GlobalObjectId` BLOB (read from `reader`'s current position) back into the `icalUid` it was
|
|
67
|
+
* built from - unwrapping the `VCALID` form (see this file's own doc comment) when `Data` carries the
|
|
68
|
+
* `"vCal-Uid"` marker, falling back to treating `Data` as bare `OutlookID` bytes otherwise. Throws if
|
|
69
|
+
* `ByteArrayID` doesn't match the spec's own fixed constant - a real, spec-mandated identity check, not an
|
|
70
|
+
* invented restriction. */
|
|
71
|
+
export function decodeGlobalObjectId(reader) {
|
|
72
|
+
const byteArrayId = reader.readBytes(16);
|
|
73
|
+
if (!byteArrayId.equals(BYTE_ARRAY_ID)) {
|
|
74
|
+
throw new Error("GlobalObjectId: ByteArrayID did not match the required [MS-OXOCAL] constant.");
|
|
75
|
+
}
|
|
76
|
+
reader.readUInt8(); // YH
|
|
77
|
+
reader.readUInt8(); // YL
|
|
78
|
+
reader.readUInt8(); // M
|
|
79
|
+
reader.readUInt8(); // D
|
|
80
|
+
reader.readBigUInt64LE(); // CreationTime - not needed to recover icalUid
|
|
81
|
+
reader.readBytes(8); // X
|
|
82
|
+
const size = reader.readUInt32LE();
|
|
83
|
+
const data = reader.readBytes(size);
|
|
84
|
+
const isVCalId = size >= VCAL_MIN_DATA_LENGTH && data.subarray(0, 8).equals(VCAL_MARKER) && data.subarray(8, 12).equals(VCAL_VERSION);
|
|
85
|
+
if (isVCalId) {
|
|
86
|
+
// Strip the marker+version prefix and the trailing NUL, per [MS-ASEMAIL]'s own UID-length formula
|
|
87
|
+
// (BYTECOUNT minus the marker minus the version minus 1 byte for the NUL).
|
|
88
|
+
return data.subarray(12, data.length - 1).toString("utf-8");
|
|
89
|
+
}
|
|
90
|
+
return data.toString("utf-8");
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=GlobalObjectId.js.map
|