-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathmsg-parser.ts
More file actions
128 lines (108 loc) · 4.7 KB
/
msg-parser.ts
File metadata and controls
128 lines (108 loc) · 4.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { CompoundFile } from "./compound-file/compound-file";
import { TEXT_DECODER } from "./compound-file/constants/text-decoder";
import type { DirectoryEntry } from "./compound-file/directory/types/directory-entry";
import { ATTACH_PROPERTIES, CODEPAGE_PROPERTY, CODEPAGES, PropertySource, RECIP_PROPERTIES, ROOT_PROPERTIES, type Property } from "./streams/property/properties";
import { getPropertyStreamEntry } from "./streams/property/property-stream";
import { PtypBinary, PtypObject, PtypString, PtypString8, PtypTime, type PropertyType } from "./streams/property/property-types";
import type { PropertyStreamEntry } from "./streams/property/types/property-stream-entry";
import type { Attachment, Message, MessageContent, Recipient } from "./types/message";
export function parse(view: DataView): Message {
const file = CompoundFile.create(view);
const dir = file.directory.entries[0];
return parseDir(file, dir);
}
export function parseDir(file: CompoundFile, dir: DirectoryEntry): Message {
const pStreamEntry = getPropertyStreamEntry(file, dir)!;
return {
file: file,
content: getContent(file, dir, pStreamEntry),
attachments: getAttachments(file, dir),
recipients: getRecipients(file, dir),
};
}
function getContent(file: CompoundFile, dir: DirectoryEntry, pStreamEntry: PropertyStreamEntry): MessageContent {
const codepage = getCodepage(file, dir, pStreamEntry);
return getValue(file, ROOT_PROPERTIES, dir, pStreamEntry, codepage);
}
function getRecipients(file: CompoundFile, dir: DirectoryEntry): Recipient[] {
return getValues(file, dir, RECIP_PROPERTIES, "recip");
}
function getAttachments(file: CompoundFile, dir: DirectoryEntry): Attachment[] {
return getValues(file, dir, ATTACH_PROPERTIES, "attach");
}
function getValues<T>(file: CompoundFile, dir: DirectoryEntry, properties: Property[], prefix: string): T[] {
const list: T[] = [];
for (let i = 0; i < 2048; i++) {
const directory = file.directory.get(`__${prefix}_version1.0_#${i.toString(16).padStart(8, "0")}`, dir.childId, false);
if (!directory) break;
const pStreamEntry = getPropertyStreamEntry(file, directory)!;
list.push(getValue(file, properties, directory, pStreamEntry));
}
return list;
}
function getCodepage(file: CompoundFile, dir: DirectoryEntry, entry: PropertyStreamEntry): number | undefined {
return getValue<{ codepage: number | undefined }>(file, [CODEPAGE_PROPERTY], dir, entry).codepage;
}
function getValue<T>(file: CompoundFile, properties: Property[], dir: DirectoryEntry, entry: PropertyStreamEntry, codepage?: number): T {
return properties.reduce((acc, p) => {
if (p.source == PropertySource.Stream) {
for (const ptype of p.types) {
const streamName = `__substg1.0_${p.id.padStart(4, "0")}${ptype.id.toString(16).padStart(4, "0")}`;
const entry = file.directory.get(streamName, dir.childId, false);
if (entry) {
acc[p.name as keyof T] = getValueFromStream(file, entry, ptype, codepage) as T[keyof T];
break;
}
}
} else {
const value = getValueFromProperty(entry, p);
if (!value) return acc;
acc[p.name as keyof T] = value as T[keyof T];
}
return acc;
}, {} as T);
}
function getValueFromProperty(entry: PropertyStreamEntry, property: Property) {
const value = entry.data.get(property.id.toLowerCase())?.valueOrSize;
if (!value) return "";
switch (property.types[0]) {
case PtypTime: {
// Subtracting the number of seconds between January 1, 1601 and January 1, 1970.
return new Date(Number(value as bigint / 10000n) - 1.16444736e13);
}
default: return value;
}
}
function getValueFromStream(file: CompoundFile, entry: DirectoryEntry, type: PropertyType, codepage?: number): string | DataView | DirectoryEntry | null {
switch (type) {
case PtypString: {
let value = "";
file.readStream(entry, (offset, bytes) => {
value += TEXT_DECODER.decode(new DataView(file.view.buffer, offset, bytes));
});
return value;
};
case PtypString8: {
const decoder = new TextDecoder(CODEPAGES.get(codepage || 65001));
let value = "";
file.readStream(entry, (offset, bytes) => {
value += decoder.decode(new DataView(file.view.buffer, offset, bytes));
});
return value;
};
case PtypBinary: {
const chunks = new Uint8Array(Number(entry.streamSize));
let pos = 0;
file.readStream(entry, (offset, bytes) => {
const chunk = file.view.buffer.slice(offset, offset + bytes);
chunks.set(new Uint8Array(chunk), pos);
pos += bytes;
});
return new DataView(chunks.buffer);
};
case PtypObject: {
return entry;
}
default: return null;
};
}