@rapidmx/mapi-plugin 1.0.0-beta.10
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 +489 -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 +475 -0
- package/dist/lib/MapiSessionManager.js.map +1 -0
- package/dist/lib/RegexPatternUtils.js +30 -0
- package/dist/lib/RegexPatternUtils.js.map +1 -0
- package/dist/lib/RopDispatcher.js +145 -0
- package/dist/lib/RopDispatcher.js.map +1 -0
- package/dist/lib/codec/AppointmentRecurrence.js +315 -0
- package/dist/lib/codec/AppointmentRecurrence.js.map +1 -0
- package/dist/lib/codec/BufferCursor.js +242 -0
- package/dist/lib/codec/BufferCursor.js.map +1 -0
- package/dist/lib/codec/GlobalObjectId.js +98 -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 +126 -0
- package/dist/lib/codec/MapiTimeZone.js.map +1 -0
- package/dist/lib/codec/PropertyValue.js +240 -0
- package/dist/lib/codec/PropertyValue.js.map +1 -0
- package/dist/lib/codec/RopBuffer.js +38 -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 +85 -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/AddressList.js +114 -0
- package/dist/lib/rop/AddressList.js.map +1 -0
- package/dist/lib/rop/CalendarEventTarget.js +30 -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 +19 -0
- package/dist/lib/rop/ContactTarget.js.map +1 -0
- package/dist/lib/rop/ContentsTable.js +60 -0
- package/dist/lib/rop/ContentsTable.js.map +1 -0
- package/dist/lib/rop/ExecuteBudget.js +82 -0
- package/dist/lib/rop/ExecuteBudget.js.map +1 -0
- package/dist/lib/rop/FastTransferStream.js +195 -0
- package/dist/lib/rop/FastTransferStream.js.map +1 -0
- package/dist/lib/rop/FolderTarget.js +118 -0
- package/dist/lib/rop/FolderTarget.js.map +1 -0
- package/dist/lib/rop/HandleDataCache.js +412 -0
- package/dist/lib/rop/HandleDataCache.js.map +1 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js +241 -0
- package/dist/lib/rop/MeetingMessageClassHandler.js.map +1 -0
- package/dist/lib/rop/MessageBodyStream.js +68 -0
- package/dist/lib/rop/MessageBodyStream.js.map +1 -0
- package/dist/lib/rop/MessageTarget.js +57 -0
- package/dist/lib/rop/MessageTarget.js.map +1 -0
- package/dist/lib/rop/NamedPropertyRegistry.js +69 -0
- package/dist/lib/rop/NamedPropertyRegistry.js.map +1 -0
- package/dist/lib/rop/PropertyResolvers.js +353 -0
- package/dist/lib/rop/PropertyResolvers.js.map +1 -0
- package/dist/lib/rop/RepoPaging.js +54 -0
- package/dist/lib/rop/RepoPaging.js.map +1 -0
- package/dist/lib/rop/RestapiRules.js +52 -0
- package/dist/lib/rop/RestapiRules.js.map +1 -0
- package/dist/lib/rop/RopCreateMessageHandler.js +55 -0
- package/dist/lib/rop/RopCreateMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js +174 -0
- package/dist/lib/rop/RopDeleteFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopDeleteMessagesHandler.js +110 -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 +60 -0
- package/dist/lib/rop/RopFastTransferSourceCopyToHandler.js.map +1 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js +76 -0
- package/dist/lib/rop/RopFastTransferSourceGetBufferHandler.js.map +1 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js +55 -0
- package/dist/lib/rop/RopGetContentsTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js +42 -0
- package/dist/lib/rop/RopGetHierarchyTableHandler.js.map +1 -0
- package/dist/lib/rop/RopGetPropertiesSpecificHandler.js +56 -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 +6 -0
- package/dist/lib/rop/RopHandler.js.map +1 -0
- package/dist/lib/rop/RopLogonHandler.js +135 -0
- package/dist/lib/rop/RopLogonHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenFolderHandler.js +48 -0
- package/dist/lib/rop/RopOpenFolderHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenMessageHandler.js +83 -0
- package/dist/lib/rop/RopOpenMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopOpenStreamHandler.js +84 -0
- package/dist/lib/rop/RopOpenStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopQueryRowsHandler.js +120 -0
- package/dist/lib/rop/RopQueryRowsHandler.js.map +1 -0
- package/dist/lib/rop/RopReadStreamHandler.js +73 -0
- package/dist/lib/rop/RopReadStreamHandler.js.map +1 -0
- package/dist/lib/rop/RopReleaseHandler.js +23 -0
- package/dist/lib/rop/RopReleaseHandler.js.map +1 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js +269 -0
- package/dist/lib/rop/RopSaveChangesMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopSetColumnsHandler.js +50 -0
- package/dist/lib/rop/RopSetColumnsHandler.js.map +1 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js +173 -0
- package/dist/lib/rop/RopSetPropertiesHandler.js.map +1 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js +342 -0
- package/dist/lib/rop/RopSubmitMessageHandler.js.map +1 -0
- package/dist/lib/rop/RopWriteStreamHandler.js +72 -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 +15 -0
- package/dist/lib/rop/TaskTarget.js.map +1 -0
- package/dist/lib/rop/TransportSend.js +16 -0
- package/dist/lib/rop/TransportSend.js.map +1 -0
- package/dist/lib/sql/MapiEmsmdbRouteSQL.js +85 -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 +139 -0
- package/dist/types/BaseMapiNspiRoute.d.ts +38 -0
- package/dist/types/MapiSessionManager.d.ts +285 -0
- package/dist/types/RegexPatternUtils.d.ts +14 -0
- package/dist/types/RopDispatcher.d.ts +53 -0
- package/dist/types/codec/AppointmentRecurrence.d.ts +14 -0
- package/dist/types/codec/BufferCursor.d.ts +92 -0
- package/dist/types/codec/GlobalObjectId.d.ts +15 -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 +87 -0
- package/dist/types/codec/RopBuffer.d.ts +32 -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 +20 -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 +26 -0
- package/dist/types/rop/AddressList.d.ts +48 -0
- package/dist/types/rop/CalendarEventTarget.d.ts +33 -0
- package/dist/types/rop/CalendarNamedProperties.d.ts +37 -0
- package/dist/types/rop/ContactTarget.d.ts +29 -0
- package/dist/types/rop/ContentsTable.d.ts +16 -0
- package/dist/types/rop/ExecuteBudget.d.ts +48 -0
- package/dist/types/rop/FastTransferStream.d.ts +57 -0
- package/dist/types/rop/FolderTarget.d.ts +64 -0
- package/dist/types/rop/HandleDataCache.d.ts +145 -0
- package/dist/types/rop/MeetingMessageClassHandler.d.ts +30 -0
- package/dist/types/rop/MessageBodyStream.d.ts +35 -0
- package/dist/types/rop/MessageTarget.d.ts +45 -0
- package/dist/types/rop/NamedPropertyRegistry.d.ts +50 -0
- package/dist/types/rop/PropertyResolvers.d.ts +86 -0
- package/dist/types/rop/RepoPaging.d.ts +30 -0
- package/dist/types/rop/RestapiRules.d.ts +32 -0
- package/dist/types/rop/RopCreateMessageHandler.d.ts +24 -0
- package/dist/types/rop/RopDeleteFolderHandler.d.ts +53 -0
- package/dist/types/rop/RopDeleteMessagesHandler.d.ts +43 -0
- package/dist/types/rop/RopFastTransferSourceCopyPropertiesHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceCopyToHandler.d.ts +25 -0
- package/dist/types/rop/RopFastTransferSourceGetBufferHandler.d.ts +22 -0
- package/dist/types/rop/RopGetContentsTableHandler.d.ts +25 -0
- package/dist/types/rop/RopGetHierarchyTableHandler.d.ts +18 -0
- package/dist/types/rop/RopGetPropertiesSpecificHandler.d.ts +26 -0
- package/dist/types/rop/RopGetPropertyIdsFromNamesHandler.d.ts +23 -0
- package/dist/types/rop/RopHandler.d.ts +106 -0
- package/dist/types/rop/RopLogonHandler.d.ts +31 -0
- package/dist/types/rop/RopOpenFolderHandler.d.ts +20 -0
- package/dist/types/rop/RopOpenMessageHandler.d.ts +28 -0
- package/dist/types/rop/RopOpenStreamHandler.d.ts +28 -0
- package/dist/types/rop/RopQueryRowsHandler.d.ts +43 -0
- package/dist/types/rop/RopReadStreamHandler.d.ts +20 -0
- package/dist/types/rop/RopReleaseHandler.d.ts +15 -0
- package/dist/types/rop/RopSaveChangesMessageHandler.d.ts +59 -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 +80 -0
- package/dist/types/rop/RopWriteStreamHandler.d.ts +31 -0
- package/dist/types/rop/TaskNamedProperties.d.ts +24 -0
- package/dist/types/rop/TaskTarget.d.ts +24 -0
- package/dist/types/rop/TransportSend.d.ts +10 -0
- package/dist/types/sql/MapiEmsmdbRouteSQL.d.ts +20 -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,242 @@
|
|
|
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
|
+
/**
|
|
23
|
+
* A request that can't be decoded: truncated, or carrying a value (such as an unknown property type) whose wire
|
|
24
|
+
* size can't be determined. `RopDispatcher` answers the whole `Execute` with a 400 for one of these, since the bytes
|
|
25
|
+
* of every later ROP can no longer be located.
|
|
26
|
+
*/
|
|
27
|
+
export class DecodeError extends RangeError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = "DecodeError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class BufferReader {
|
|
34
|
+
constructor(buffer, offset = 0) {
|
|
35
|
+
this.buffer = buffer;
|
|
36
|
+
this.offset = offset;
|
|
37
|
+
}
|
|
38
|
+
get position() {
|
|
39
|
+
return this.offset;
|
|
40
|
+
}
|
|
41
|
+
get remaining() {
|
|
42
|
+
return this.buffer.length - this.offset;
|
|
43
|
+
}
|
|
44
|
+
hasMore() {
|
|
45
|
+
return this.offset < this.buffer.length;
|
|
46
|
+
}
|
|
47
|
+
/** Moves to `position` (backwards or forwards). Throws a `RangeError` for a position outside the buffer. */
|
|
48
|
+
seek(position) {
|
|
49
|
+
if (position < 0 || position > this.buffer.length) {
|
|
50
|
+
throw new RangeError(`BufferReader.seek(${position}): out of range (buffer length ${this.buffer.length}).`);
|
|
51
|
+
}
|
|
52
|
+
this.offset = position;
|
|
53
|
+
}
|
|
54
|
+
readUInt8() {
|
|
55
|
+
const value = this.buffer.readUInt8(this.offset);
|
|
56
|
+
this.offset += 1;
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
readInt16LE() {
|
|
60
|
+
const value = this.buffer.readInt16LE(this.offset);
|
|
61
|
+
this.offset += 2;
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
readUInt16LE() {
|
|
65
|
+
const value = this.buffer.readUInt16LE(this.offset);
|
|
66
|
+
this.offset += 2;
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
readInt32LE() {
|
|
70
|
+
const value = this.buffer.readInt32LE(this.offset);
|
|
71
|
+
this.offset += 4;
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
readUInt32LE() {
|
|
75
|
+
const value = this.buffer.readUInt32LE(this.offset);
|
|
76
|
+
this.offset += 4;
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
readFloatLE() {
|
|
80
|
+
const value = this.buffer.readFloatLE(this.offset);
|
|
81
|
+
this.offset += 4;
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
readDoubleLE() {
|
|
85
|
+
const value = this.buffer.readDoubleLE(this.offset);
|
|
86
|
+
this.offset += 8;
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
readBigInt64LE() {
|
|
90
|
+
const value = this.buffer.readBigInt64LE(this.offset);
|
|
91
|
+
this.offset += 8;
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
readBigUInt64LE() {
|
|
95
|
+
const value = this.buffer.readBigUInt64LE(this.offset);
|
|
96
|
+
this.offset += 8;
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
/** Unlike every fixed-width `readXxxLE` above, `Buffer.prototype.subarray` never throws - an out-of-range
|
|
100
|
+
* `length` (negative, or reaching past the buffer's end) silently clamps instead, which would otherwise let
|
|
101
|
+
* a malformed/truncated ROP buffer produce garbage-but-not-erroring reads (a negative `length` even rewinds
|
|
102
|
+
* `offset` backwards) instead of failing loudly the way this class's own doc comment promises. Explicitly
|
|
103
|
+
* bounds-checked here for that reason. */
|
|
104
|
+
readBytes(length) {
|
|
105
|
+
if (length < 0 || this.offset + length > this.buffer.length) {
|
|
106
|
+
throw new RangeError(`BufferReader.readBytes(${length}): out of range at offset ${this.offset} (buffer length ${this.buffer.length}).`);
|
|
107
|
+
}
|
|
108
|
+
const value = this.buffer.subarray(this.offset, this.offset + length);
|
|
109
|
+
this.offset += length;
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
/** Reads a UTF-16LE string up to (and consuming) its terminating `0x0000` code unit - `PtypString`'s wire
|
|
113
|
+
* encoding. Scans on 2-byte boundaries, since a null code unit's low/high byte pair can't be mistaken for
|
|
114
|
+
* one half of a non-null UTF-16 code unit at an odd offset the way a naive single-byte scan could.
|
|
115
|
+
*
|
|
116
|
+
* Throws if no terminator is found before running out of buffer, rather than silently returning whatever
|
|
117
|
+
* content preceded the truncation and leaving `offset` past the buffer's actual end - a truncated/malformed
|
|
118
|
+
* string is exactly the kind of input this class's own doc comment says should fail loudly. This also
|
|
119
|
+
* closes a real DoS: `PropertyValue.ts`'s `readCountedArray` loops a client-controlled element count with
|
|
120
|
+
* no independent cap, trusting each element's own reader to terminate the loop early against a too-small
|
|
121
|
+
* buffer the way every fixed-width reader already does - without this, `PtypMultipleString`'s
|
|
122
|
+
* `readNullTerminatedUtf16LE` calls kept silently returning `""` past the buffer's end forever instead. */
|
|
123
|
+
readNullTerminatedUtf16LE() {
|
|
124
|
+
let end = this.offset;
|
|
125
|
+
while (end + 1 < this.buffer.length && !(this.buffer[end] === 0 && this.buffer[end + 1] === 0)) {
|
|
126
|
+
end += 2;
|
|
127
|
+
}
|
|
128
|
+
if (end + 1 >= this.buffer.length) {
|
|
129
|
+
throw new RangeError("BufferReader.readNullTerminatedUtf16LE(): no null terminator found before the end of the buffer.");
|
|
130
|
+
}
|
|
131
|
+
const value = this.buffer.toString("utf16le", this.offset, end);
|
|
132
|
+
this.offset = end + 2;
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
/** Reads a single-byte-terminated 8-bit string (`PtypString8`'s wire encoding). Multibyte string content
|
|
136
|
+
* itself is treated as UTF-8, a pragmatic choice documented in `PropertyValue.ts` rather than the
|
|
137
|
+
* "externally specified encoding" the spec leaves open-ended.
|
|
138
|
+
*
|
|
139
|
+
* Throws if no terminator is found before running out of buffer - see `readNullTerminatedUtf16LE`'s own
|
|
140
|
+
* doc comment for why (identical reasoning, including the `readCountedArray` DoS this closes). */
|
|
141
|
+
readNullTerminatedString8() {
|
|
142
|
+
let end = this.offset;
|
|
143
|
+
while (end < this.buffer.length && this.buffer[end] !== 0) {
|
|
144
|
+
end += 1;
|
|
145
|
+
}
|
|
146
|
+
if (end >= this.buffer.length) {
|
|
147
|
+
throw new RangeError("BufferReader.readNullTerminatedString8(): no null terminator found before the end of the buffer.");
|
|
148
|
+
}
|
|
149
|
+
const value = this.buffer.toString("utf-8", this.offset, end);
|
|
150
|
+
this.offset = end + 1;
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** The write-side counterpart to `BufferReader`, accumulating chunks and concatenating once via `toBuffer()` -
|
|
155
|
+
* the same "array of pieces, `Buffer.concat` once at the end" idiom `WbxmlEncoder` already uses for its own
|
|
156
|
+
* byte-array accumulator, adapted for multi-byte little-endian fields instead of single bytes. */
|
|
157
|
+
export class BufferWriter {
|
|
158
|
+
constructor() {
|
|
159
|
+
this.chunks = [];
|
|
160
|
+
this.byteLength = 0;
|
|
161
|
+
}
|
|
162
|
+
/** The number of bytes written so far. */
|
|
163
|
+
get length() {
|
|
164
|
+
return this.byteLength;
|
|
165
|
+
}
|
|
166
|
+
push(...buffers) {
|
|
167
|
+
for (const buffer of buffers) {
|
|
168
|
+
this.chunks.push(buffer);
|
|
169
|
+
this.byteLength += buffer.length;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
writeUInt8(value) {
|
|
173
|
+
const buf = Buffer.alloc(1);
|
|
174
|
+
buf.writeUInt8(value, 0);
|
|
175
|
+
this.push(buf);
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
writeInt16LE(value) {
|
|
179
|
+
const buf = Buffer.alloc(2);
|
|
180
|
+
buf.writeInt16LE(value, 0);
|
|
181
|
+
this.push(buf);
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
writeUInt16LE(value) {
|
|
185
|
+
const buf = Buffer.alloc(2);
|
|
186
|
+
buf.writeUInt16LE(value, 0);
|
|
187
|
+
this.push(buf);
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
writeInt32LE(value) {
|
|
191
|
+
const buf = Buffer.alloc(4);
|
|
192
|
+
buf.writeInt32LE(value, 0);
|
|
193
|
+
this.push(buf);
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
writeUInt32LE(value) {
|
|
197
|
+
const buf = Buffer.alloc(4);
|
|
198
|
+
buf.writeUInt32LE(value, 0);
|
|
199
|
+
this.push(buf);
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
writeFloatLE(value) {
|
|
203
|
+
const buf = Buffer.alloc(4);
|
|
204
|
+
buf.writeFloatLE(value, 0);
|
|
205
|
+
this.push(buf);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
writeDoubleLE(value) {
|
|
209
|
+
const buf = Buffer.alloc(8);
|
|
210
|
+
buf.writeDoubleLE(value, 0);
|
|
211
|
+
this.push(buf);
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
writeBigInt64LE(value) {
|
|
215
|
+
const buf = Buffer.alloc(8);
|
|
216
|
+
buf.writeBigInt64LE(value, 0);
|
|
217
|
+
this.push(buf);
|
|
218
|
+
return this;
|
|
219
|
+
}
|
|
220
|
+
writeBigUInt64LE(value) {
|
|
221
|
+
const buf = Buffer.alloc(8);
|
|
222
|
+
buf.writeBigUInt64LE(value, 0);
|
|
223
|
+
this.push(buf);
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
writeBytes(value) {
|
|
227
|
+
this.push(value);
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
writeNullTerminatedUtf16LE(value) {
|
|
231
|
+
this.push(Buffer.from(value, "utf16le"), Buffer.from([0x00, 0x00]));
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
writeNullTerminatedString8(value) {
|
|
235
|
+
this.push(Buffer.from(value, "utf-8"), Buffer.from([0x00]));
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
toBuffer() {
|
|
239
|
+
return Buffer.concat(this.chunks);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
//# 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,98 @@
|
|
|
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 the hex form below only when the `vCal-Uid` marker isn't present, i.e. an
|
|
22
|
+
* `OutlookID` - a native-Exchange-generated `GlobalObjectId`, decoded to the hex form `[MS-OXCICAL]` gives its
|
|
23
|
+
* iCalendar UID). `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` (set when a `GlobalObjectId` identifies a single occurrence of a recurring
|
|
28
|
+
* series) are always encoded as `0` ("not an exception"); `globalObjectIdInstanceDate` reads them from a client's
|
|
29
|
+
* meeting response.
|
|
30
|
+
* `PidLidCleanGlobalObjectId` (the sibling property real Outlook also sets, identical structure but with
|
|
31
|
+
* `YH`/`YL`/`M`/`D` always zeroed) is therefore byte-identical to this codec's own output and needs no separate
|
|
32
|
+
* implementation - the same value serves both properties.
|
|
33
|
+
*
|
|
34
|
+
* @author Jean-Philippe Steinmetz
|
|
35
|
+
*/
|
|
36
|
+
/** `ByteArrayID` (`[MS-OXOCAL]` §2.2.1.27): a fixed 16-byte constant identifying this BLOB as a `GlobalObjectId`
|
|
37
|
+
* - the spec's own exact required byte sequence, not invented. */
|
|
38
|
+
const BYTE_ARRAY_ID = Buffer.from([0x04, 0x00, 0x00, 0x00, 0x82, 0x00, 0xe0, 0x00, 0x74, 0xc5, 0xb7, 0x10, 0x1a, 0x82, 0xe0, 0x08]);
|
|
39
|
+
/** `VCALID`'s own fixed 8-byte ASCII marker (`[MS-ASEMAIL]` §2.2.2.37's `VCALSTRING`) identifying `Data` as a
|
|
40
|
+
* `"vCal-Uid"`-wrapped UID rather than a native `OutlookID`'s raw bytes. */
|
|
41
|
+
const VCAL_MARKER = Buffer.from("vCal-Uid", "ascii");
|
|
42
|
+
/** `VCALID`'s own fixed 4-byte `VERSION` field - always this exact literal byte sequence, not a real version
|
|
43
|
+
* negotiated at runtime. */
|
|
44
|
+
const VCAL_VERSION = Buffer.from([0x01, 0x00, 0x00, 0x00]);
|
|
45
|
+
/** A `VCALID`'s minimum possible `Data` length: `VCAL_MARKER` (8) + `VCAL_VERSION` (4) + a zero-length UID (0)
|
|
46
|
+
* + the trailing `NUL` (1). */
|
|
47
|
+
const VCAL_MIN_DATA_LENGTH = VCAL_MARKER.length + VCAL_VERSION.length + 1;
|
|
48
|
+
/** Encodes `icalUid` into a `GlobalObjectId` BLOB in the `VCALID` form a real client synthesizes from a plain
|
|
49
|
+
* iCalendar UID (see this file's own doc comment for why that's the form that matters here), using `at` as the
|
|
50
|
+
* `CreationTime`. */
|
|
51
|
+
export function encodeGlobalObjectId(icalUid, at) {
|
|
52
|
+
const data = Buffer.concat([VCAL_MARKER, VCAL_VERSION, Buffer.from(icalUid, "utf-8"), Buffer.from([0x00])]);
|
|
53
|
+
const writer = new BufferWriter();
|
|
54
|
+
writer.writeBytes(BYTE_ARRAY_ID);
|
|
55
|
+
writer.writeUInt8(0); // YH - not an exception, see class doc comment
|
|
56
|
+
writer.writeUInt8(0); // YL
|
|
57
|
+
writer.writeUInt8(0); // M
|
|
58
|
+
writer.writeUInt8(0); // D
|
|
59
|
+
writer.writeBigUInt64LE(dateToFiletime(at)); // CreationTime
|
|
60
|
+
writer.writeBytes(Buffer.alloc(8)); // X - reserved, MUST be zero
|
|
61
|
+
writer.writeUInt32LE(data.length); // Size
|
|
62
|
+
writer.writeBytes(data);
|
|
63
|
+
return writer.toBuffer();
|
|
64
|
+
}
|
|
65
|
+
/** Decodes a `GlobalObjectId` BLOB (read from `reader`'s current position) back into the `icalUid` it stands for.
|
|
66
|
+
* A `VCALID` (see this file's own doc comment) yields the UID it wraps. Anything else is a native `OutlookID`,
|
|
67
|
+
* whose `Data` is binary, not text: per `[MS-OXCICAL]`'s UID conversion, the iCalendar UID of such an object is the
|
|
68
|
+
* uppercase hex of the whole `GlobalObjectId` with the instance date (`YH`/`YL`/`M`/`D`, bytes 16-19) zeroed, so
|
|
69
|
+
* that is what is returned (compare it case-insensitively). Throws if `ByteArrayID` doesn't match the spec's own
|
|
70
|
+
* fixed constant - a real, spec-mandated identity check, not an 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.readBytes(4); // YH/YL/M/D
|
|
77
|
+
const creationTimeAndReserved = reader.readBytes(16); // CreationTime + X
|
|
78
|
+
const sizeBytes = reader.readBytes(4);
|
|
79
|
+
const size = sizeBytes.readUInt32LE(0);
|
|
80
|
+
const data = reader.readBytes(size);
|
|
81
|
+
const isVCalId = size >= VCAL_MIN_DATA_LENGTH && data.subarray(0, 8).equals(VCAL_MARKER) && data.subarray(8, 12).equals(VCAL_VERSION);
|
|
82
|
+
if (isVCalId) {
|
|
83
|
+
// Strip the marker+version prefix and the trailing NUL, per [MS-ASEMAIL]'s own UID-length formula
|
|
84
|
+
// (BYTECOUNT minus the marker minus the version minus 1 byte for the NUL).
|
|
85
|
+
return data.subarray(12, data.length - 1).toString("utf-8");
|
|
86
|
+
}
|
|
87
|
+
return Buffer.concat([byteArrayId, Buffer.alloc(4), creationTimeAndReserved, sizeBytes, data]).toString("hex").toUpperCase();
|
|
88
|
+
}
|
|
89
|
+
/** The `YYYY-MM-DD` occurrence date a `GlobalObjectId` names (`YH`/`YL`/`M`/`D`), or `undefined` when it names the
|
|
90
|
+
* whole series (a zero year). `globalObjectId` must already have decoded successfully. */
|
|
91
|
+
export function globalObjectIdInstanceDate(globalObjectId) {
|
|
92
|
+
const year = (globalObjectId[16] << 8) | globalObjectId[17];
|
|
93
|
+
if (year === 0) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return `${String(year).padStart(4, "0")}-${String(globalObjectId[18]).padStart(2, "0")}-${String(globalObjectId[19]).padStart(2, "0")}`;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=GlobalObjectId.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalObjectId.js","sourceRoot":"","sources":["../../../src/codec/GlobalObjectId.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Encodes/decodes the 16-byte binary GUID layout `[MS-DTYP]`'s `GUID` structure defines and every MAPI wire
|
|
8
|
+
* structure that embeds a GUID (EntryIDs, `PidTagStoreRecordKey`, `PtypGuid` property values, property set
|
|
9
|
+
* IDs) uses: `Data1`/`Data2`/`Data3` in little-endian order, followed by `Data4`'s 8 bytes unchanged (the same
|
|
10
|
+
* order they appear in a standard textual GUID's last two hyphen-separated groups). This is distinct from a
|
|
11
|
+
* plain RFC 4122 UUID's all-big-endian textual byte order, so a generic UUID library's byte form can't be
|
|
12
|
+
* reused directly - hand-built here, same "no library, build the wire format precisely" precedent as the
|
|
13
|
+
* WBXML codec and the Autodiscover XML helpers.
|
|
14
|
+
*
|
|
15
|
+
* @author Jean-Philippe Steinmetz
|
|
16
|
+
*/
|
|
17
|
+
const GUID_STRING_PATTERN = /^([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})$/i;
|
|
18
|
+
/** Encodes a standard hyphenated GUID/UUID string into its 16-byte MS wire form. */
|
|
19
|
+
export function encodeGuid(guid) {
|
|
20
|
+
const match = GUID_STRING_PATTERN.exec(guid);
|
|
21
|
+
if (!match) {
|
|
22
|
+
throw new Error(`Invalid GUID string: ${guid}`);
|
|
23
|
+
}
|
|
24
|
+
const [, data1, data2, data3, data4a, data4b] = match;
|
|
25
|
+
const writer = new BufferWriter();
|
|
26
|
+
writer.writeUInt32LE(parseInt(data1, 16));
|
|
27
|
+
writer.writeUInt16LE(parseInt(data2, 16));
|
|
28
|
+
writer.writeUInt16LE(parseInt(data3, 16));
|
|
29
|
+
writer.writeBytes(Buffer.from(data4a + data4b, "hex"));
|
|
30
|
+
return writer.toBuffer();
|
|
31
|
+
}
|
|
32
|
+
/** Decodes a 16-byte MS wire-form GUID (read from `reader`'s current position) into a standard hyphenated
|
|
33
|
+
* GUID/UUID string. */
|
|
34
|
+
export function decodeGuid(reader) {
|
|
35
|
+
const data1 = reader.readUInt32LE().toString(16).padStart(8, "0");
|
|
36
|
+
const data2 = reader.readUInt16LE().toString(16).padStart(4, "0");
|
|
37
|
+
const data3 = reader.readUInt16LE().toString(16).padStart(4, "0");
|
|
38
|
+
const data4 = reader.readBytes(8).toString("hex");
|
|
39
|
+
return `${data1}-${data2}-${data3}-${data4.slice(0, 4)}-${data4.slice(4)}`;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=MapiGuid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapiGuid.js","sourceRoot":"","sources":["../../../src/codec/MapiGuid.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,mEAAmE;AACnE,mCAAmC;AACnC,+EAA+E;AAC/E,OAAO,EAAgB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,MAAM,mBAAmB,GAAG,2EAA2E,CAAC;AAExG,oFAAoF;AACpF,MAAM,UAAU,UAAU,CAAC,IAAY;IACnC,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IAEtD,MAAM,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;IAClC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1C,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IACvD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC7B,CAAC;AAED;uBACuB;AACvB,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClD,OAAO,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,126 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Encodes/decodes `CalendarEvent.timezone` (a plain IANA zone identifier) to/from `PidLidTimeZoneStruct`'s
|
|
8
|
+
* `TimeZoneStruct` BLOB (`[MS-OXOCAL]` §2.2.1.39, confirmed field-by-field this session, including a real
|
|
9
|
+
* worked example: `lBias(4,LONG)+lStandardBias(4,LONG)+lDaylightBias(4,LONG)+wStandardYear(2,WORD)+
|
|
10
|
+
* stStandardDate(16,SYSTEMTIME)+wDaylightYear(2,WORD)+stDaylightDate(16,SYSTEMTIME)` = 48 bytes total, all
|
|
11
|
+
* little-endian. `SYSTEMTIME` (`[MS-DTYP]`) is 8 `WORD` fields (`wYear`/`wMonth`/`wDayOfWeek`/`wDay`/`wHour`/
|
|
12
|
+
* `wMinute`/`wSecond`/`wMilliseconds`), 16 bytes.
|
|
13
|
+
*
|
|
14
|
+
* This is deliberately the older, simpler `TimeZoneStruct` - not the newer `TimeZoneDefinition`/
|
|
15
|
+
* `PidLidAppointmentTimeZoneDefinitionStartDisplay`, which additionally encodes named DST transition rules.
|
|
16
|
+
*
|
|
17
|
+
* **Pragmatic scope, matching the same limits `CalendarSyncAdapter` (EAS) already carries for timezones**:
|
|
18
|
+
* - `"UTC"` encodes as an all-zero struct (no bias, no DST) - the spec-legitimate representation of "no offset,
|
|
19
|
+
* no DST".
|
|
20
|
+
* - Any other IANA zone is approximated as a **fixed-offset** zone: `lBias` is derived from that zone's actual
|
|
21
|
+
* UTC offset at the given reference instant (via `Intl`), with `lStandardBias`/`lDaylightBias` always `0` and
|
|
22
|
+
* both `SYSTEMTIME` transition dates all-zero (`wMonth=0`, the spec's own documented way to say "this zone
|
|
23
|
+
* does not observe daylight saving time"). **This is wrong across a real DST boundary** for a zone that
|
|
24
|
+
* actually observes DST - a real client will show the correct offset only for occurrences near the reference
|
|
25
|
+
* instant used at encode time, not a spec violation but a real, documented fidelity gap.
|
|
26
|
+
* - Decoding a struct back into an IANA identifier is fundamentally lossy (a bias alone doesn't identify a
|
|
27
|
+
* unique zone name) - this codec resolves a whole-hour offset to a synthetic-but-valid `Etc/GMT±N` IANA identifier
|
|
28
|
+
* (note the IANA `Etc/GMT` area's own sign convention is POSIX-inverted from common usage: `Etc/GMT+8` means UTC-8,
|
|
29
|
+
* not UTC+8). A half-hour/quarter-hour offset keeps its minutes: it maps to a real DST-less zone with that offset
|
|
30
|
+
* (e.g. `Asia/Kolkata`, `Asia/Kathmandu`) or otherwise to a fixed-offset identifier like `"-03:30"`. An offset
|
|
31
|
+
* outside UTC-12..UTC+14 decodes as `"UTC"`.
|
|
32
|
+
* - Encoding a zone `Intl` doesn't recognize falls back to UTC instead of throwing.
|
|
33
|
+
*
|
|
34
|
+
* @author Jean-Philippe Steinmetz
|
|
35
|
+
*/
|
|
36
|
+
const SYSTEMTIME_FIELD_COUNT = 8;
|
|
37
|
+
function writeZeroTransitionBlock(writer) {
|
|
38
|
+
writer.writeUInt16LE(0); // wStandardYear / wDaylightYear
|
|
39
|
+
for (let i = 0; i < SYSTEMTIME_FIELD_COUNT; i++) {
|
|
40
|
+
writer.writeUInt16LE(0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function skipTransitionBlock(reader) {
|
|
44
|
+
reader.readUInt16LE(); // wStandardYear / wDaylightYear
|
|
45
|
+
for (let i = 0; i < SYSTEMTIME_FIELD_COUNT; i++) {
|
|
46
|
+
reader.readUInt16LE();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** The zone's UTC offset in minutes at instant `at` (standard convention: local minus UTC, e.g. `-480` for
|
|
50
|
+
* `America/Los_Angeles` in winter), read via `Intl`'s `shortOffset` formatting rather than hand-rolling a
|
|
51
|
+
* zone database. Throws the same way `Intl.DateTimeFormat` itself does for an unrecognized zone identifier. */
|
|
52
|
+
function utcOffsetMinutes(timezone, at) {
|
|
53
|
+
const formatter = new Intl.DateTimeFormat("en-US", { timeZone: timezone, timeZoneName: "shortOffset" });
|
|
54
|
+
// `formatToParts` always includes a "timeZoneName" part when `timeZoneName` is requested in the
|
|
55
|
+
// formatter's own options, so the non-null assertion below reflects a real `Intl` guarantee, not an
|
|
56
|
+
// unverified assumption.
|
|
57
|
+
const tzName = formatter.formatToParts(at).find((part) => part.type === "timeZoneName").value;
|
|
58
|
+
const match = /^GMT([+-])(\d+)(?::(\d+))?$/.exec(tzName);
|
|
59
|
+
if (!match) {
|
|
60
|
+
return 0; // Bare "GMT", i.e. UTC itself.
|
|
61
|
+
}
|
|
62
|
+
const sign = match[1] === "-" ? -1 : 1;
|
|
63
|
+
const hours = parseInt(match[2], 10);
|
|
64
|
+
const minutes = match[3] ? parseInt(match[3], 10) : 0;
|
|
65
|
+
return sign * (hours * 60 + minutes);
|
|
66
|
+
}
|
|
67
|
+
/** Encodes `timezone` (an IANA identifier, or `"UTC"`) into a 48-byte `TimeZoneStruct` BLOB, using `at` as the
|
|
68
|
+
* reference instant for resolving a non-UTC zone's current fixed offset. */
|
|
69
|
+
export function encodeTimeZoneStruct(timezone, at) {
|
|
70
|
+
const writer = new BufferWriter();
|
|
71
|
+
let bias = 0;
|
|
72
|
+
try {
|
|
73
|
+
bias = timezone === "UTC" ? 0 : -utcOffsetMinutes(timezone, at);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// An identifier Intl doesn't recognize (e.g. stored by another client) is sent as UTC rather than failing
|
|
77
|
+
// the whole property read.
|
|
78
|
+
bias = 0;
|
|
79
|
+
}
|
|
80
|
+
writer.writeInt32LE(bias); // lBias
|
|
81
|
+
writer.writeInt32LE(0); // lStandardBias - no DST modeled
|
|
82
|
+
writer.writeInt32LE(0); // lDaylightBias - no DST modeled
|
|
83
|
+
writeZeroTransitionBlock(writer); // wStandardYear + stStandardDate
|
|
84
|
+
writeZeroTransitionBlock(writer); // wDaylightYear + stDaylightDate
|
|
85
|
+
return writer.toBuffer();
|
|
86
|
+
}
|
|
87
|
+
/** Decodes a `TimeZoneStruct` BLOB (read from `reader`'s current position) into an approximate IANA zone
|
|
88
|
+
* identifier - see this file's own doc comment for the lossy `Etc/GMT±N` fallback's exact rounding rules. */
|
|
89
|
+
export function decodeTimeZoneStruct(reader) {
|
|
90
|
+
const bias = reader.readInt32LE();
|
|
91
|
+
reader.readInt32LE(); // lStandardBias - not reconstructable into a real DST rule, ignored
|
|
92
|
+
reader.readInt32LE(); // lDaylightBias - ditto
|
|
93
|
+
skipTransitionBlock(reader); // wStandardYear + stStandardDate
|
|
94
|
+
skipTransitionBlock(reader); // wDaylightYear + stDaylightDate
|
|
95
|
+
const offsetMinutes = -bias;
|
|
96
|
+
// No real zone is further than UTC-12/UTC+14; anything else is garbage and treated as UTC.
|
|
97
|
+
if (offsetMinutes === 0 || offsetMinutes < -12 * 60 || offsetMinutes > 14 * 60) {
|
|
98
|
+
return "UTC";
|
|
99
|
+
}
|
|
100
|
+
if (offsetMinutes % 60 === 0) {
|
|
101
|
+
const hours = offsetMinutes / 60;
|
|
102
|
+
return `Etc/GMT${hours < 0 ? "+" : "-"}${Math.abs(hours)}`;
|
|
103
|
+
}
|
|
104
|
+
return FRACTIONAL_OFFSET_ZONES[offsetMinutes] ?? formatFixedOffset(offsetMinutes);
|
|
105
|
+
}
|
|
106
|
+
/** Real zones with a non-whole-hour offset and no daylight saving, so the zone matches a DST-less
|
|
107
|
+
* `TimeZoneStruct` all year. */
|
|
108
|
+
const FRACTIONAL_OFFSET_ZONES = {
|
|
109
|
+
[-570]: "Pacific/Marquesas",
|
|
110
|
+
210: "Asia/Tehran",
|
|
111
|
+
270: "Asia/Kabul",
|
|
112
|
+
330: "Asia/Kolkata",
|
|
113
|
+
345: "Asia/Kathmandu",
|
|
114
|
+
390: "Asia/Yangon",
|
|
115
|
+
525: "Australia/Eucla",
|
|
116
|
+
570: "Australia/Darwin",
|
|
117
|
+
};
|
|
118
|
+
/** A fixed-offset identifier such as `"-03:30"`, which `Intl.DateTimeFormat` accepts as a `timeZone`. Used for a
|
|
119
|
+
* minute-precision offset with no matching DST-less zone, instead of rounding it to a whole hour. */
|
|
120
|
+
function formatFixedOffset(offsetMinutes) {
|
|
121
|
+
const magnitude = Math.abs(offsetMinutes);
|
|
122
|
+
const hours = String(Math.floor(magnitude / 60)).padStart(2, "0");
|
|
123
|
+
const minutes = String(magnitude % 60).padStart(2, "0");
|
|
124
|
+
return `${offsetMinutes < 0 ? "-" : "+"}${hours}:${minutes}`;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=MapiTimeZone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MapiTimeZone.js","sourceRoot":"","sources":["../../../src/codec/MapiTimeZone.ts"],"names":[],"mappings":"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"}
|