@hiraokahypertools/pst_to_msg
Version:
Convert a message in Outlook PST file into MSG format
406 lines • 17.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToMsg = convertToMsg;
const Burner_1 = require("@kenjiuno/msgreader/lib/Burner");
const Reader_1 = require("@kenjiuno/msgreader/lib/Reader");
const utils_1 = require("./utils");
async function convertToMsg(message, file) {
const entries = [
{
name: "Root Entry",
type: Reader_1.TypeEnum.ROOT,
children: [],
length: 0,
},
];
await convertMessage(entries, 0, (await message.requestAccessToUserSubNode()), true, file.getStoreSupportMask());
{
const dirIndex = addDir(entries, 0, "__nameid_version1.0");
const node = (await file.requestAccessToUserNode(97));
const subNode = (await node.getSubNode());
const pc = await subNode.extractAsPropertyContext();
await convertNameId(entries, dirIndex, pc.properties, subNode, pc.resolveHeap);
}
return (0, Burner_1.burn)(entries);
}
async function convertMessage(entries, parentIndex, subNode, isRoot, storeSupportMask) {
// `__properties_version1.0`
// `__substg1.0_0E1D001F`
// `__nameid_version1.0`
// - `__substg1.0_100A0102`
// `__recip_version1.0_#00000000` ← Upper hexa 8 digits
// - `__properties_version1.0`
// - `__substg1.0_0FF60102`
// `__attach_version1.0_#00000000`
// - `__properties_version1.0`
// - `__substg1.0_0FF90102`
// - `__substg1.0_3701000D`
// - `__recip_version1.0_#00000000`
// - `__properties_version1.0`
// - `__substg1.0_0C1A001F`
let recipientsCount = 0;
const recipientsSubNode = await subNode.getSubNodeOf(0x692);
if (recipientsSubNode) {
const tc = await recipientsSubNode.extractAsTableContext();
recipientsCount = tc.numRows;
for (let y = 0; y < recipientsCount; y++) {
const dirIndex = addDir(entries, parentIndex, `__recip_version1.0_#${(0, utils_1.toHex4)(y, true)}`);
await convertGeneric(entries, dirIndex, await tc.getRow(y), recipientsSubNode, tc.resolveHeap, y);
}
}
let attachmentsCount = 0;
const attachmentsSubNode = await subNode.getSubNodeOf(0x671);
if (attachmentsSubNode) {
const tc = await attachmentsSubNode.extractAsTableContext();
attachmentsCount = tc.numRows;
for (let y = 0; y < attachmentsCount; y++) {
const dirIndex = addDir(entries, parentIndex, `__attach_version1.0_#${(0, utils_1.toHex4)(y, true)}`);
const limitedProperties = await tc.getRow(y);
const ltpRowId = limitedProperties.find((it) => it.key == 0x67f2 && it.type == 0x0003);
if (ltpRowId) {
const rowId = new DataView(ltpRowId.value).getUint32(0, true);
const attachmentSubNode = await subNode.getSubNodeOf(rowId);
if (attachmentSubNode) {
const pc = await attachmentSubNode.extractAsPropertyContext();
await convertAtt(entries, dirIndex, pc.properties, attachmentSubNode, pc.resolveHeap, y);
}
else {
throw new Error(`Attachment sub-node with row ID ${rowId} not found.`);
}
}
else {
throw new Error(`LTP row ID ${ltpRowId} not found.`);
}
}
}
{
const props1 = [];
const pc = await subNode.extractAsPropertyContext();
for (let y = 0; y < pc.properties.length; y++) {
const pair = await convertProperty(entries, parentIndex, pc.properties[y], subNode, pc.resolveHeap, false);
props1.push(pair.prop1);
}
if (isRoot) {
// At least 3 properties are mandatory for a message root:
// - PidTagStoreSupportMask
// - PidTagMessageFlags (also included in pst)
// - PidTagMessageClass (also included in pst)
const arrayBuffer = new ArrayBuffer(8);
const view = new DataView(arrayBuffer);
// from "C:\Program Files (x86)\Windows Kits\10\Include\10.0.20348.0\um\WabDefs.h"
// from https://github.com/microsoft/mfcmapi/blob/7e3111d899638da0aa594c06f5a00f980f70b0b8/core/mapi/extraPropTags.h#L1056-L1059
if (typeof storeSupportMask === "number") {
const STORE_ENTRYID_UNIQUE = 0x00000001;
const STORE_READONLY = 0x00000002;
const STORE_SEARCH_OK = 0x00000004;
const STORE_MODIFY_OK = 0x00000008;
const STORE_CREATE_OK = 0x00000010;
const STORE_ATTACH_OK = 0x00000020;
const STORE_OLE_OK = 0x00000040;
const STORE_SUBMIT_OK = 0x00000080;
const STORE_NOTIFY_OK = 0x00000100;
const STORE_MV_PROPS_OK = 0x00000200;
const STORE_CATEGORIZE_OK = 0x00000400;
const STORE_RTF_OK = 0x00000800;
const STORE_RESTRICTION_OK = 0x00001000;
const STORE_SORT_OK = 0x00002000;
const STORE_HAS_SEARCHES = 0x01000000;
const STORE_ANSI_OK = 0x00020000;
const STORE_HTML_OK = 0x00010000;
const STORE_ITEMPROC = 0x00200000;
const STORE_LOCALSTORE = 0x00080000;
const STORE_PUBLIC_FOLDERS = 0x00004000;
const STORE_PUSHER_OK = 0x00800000;
const STORE_RULES_OK = 0x10000000;
const STORE_UNCOMPRESSED_RTF = 0x00008000;
const STORE_UNICODE_OK = 0x00040000;
// // 0x40E79
// view.setUint32(0, 0
// | STORE_ENTRYID_UNIQUE | STORE_MODIFY_OK
// | STORE_CREATE_OK | STORE_ATTACH_OK | STORE_OLE_OK | STORE_MV_PROPS_OK | STORE_CATEGORIZE_OK | STORE_RTF_OK
// | STORE_UNICODE_OK,
// true
// );
view.setUint32(0, storeSupportMask, true);
// PidTagStoreSupportMask
props1.push({
tagLo: 0x0003,
tagHi: 0x340d,
mandatory: false,
readable: true,
writeable: false,
value: new Uint8Array(arrayBuffer, 0, 4),
});
}
// {
// const MSGFLAG_READ = 0x00000001;
// const MSGFLAG_UNMODIFIED = 0x00000002;
// const MSGFLAG_SUBMIT = 0x00000004;
// const MSGFLAG_UNSENT = 0x00000008;
// const MSGFLAG_HASATTACH = 0x00000010;
// const MSGFLAG_FROMME = 0x00000020;
// const MSGFLAG_ASSOCIATED = 0x00000040;
// const MSGFLAG_RESEND = 0x00000080;
// const MSGFLAG_RN_PENDING = 0x00000100;
// const MSGFLAG_NRN_PENDING = 0x00000200;
// const MSGFLAG_ORIGIN_X400 = 0x00001000;
// const MSGFLAG_ORIGIN_INTERNET = 0x00002000;
// const MSGFLAG_ORIGIN_MISC_EXT = 0x00008000;
// const MSGFLAG_OUTLOOK_NON_EMS_XP = 0x00010000;
// view.setUint32(4, MSGFLAG_READ | MSGFLAG_UNSENT, true);
// // PidTagMessageFlags
// props1.push({
// tagLo: 0x0003,
// tagHi: 0x0E07,
// mandatory: false,
// readable: true,
// writeable: false,
// value: new Uint8Array(arrayBuffer, 4, 4),
// });
// }
}
addFile(entries, parentIndex, "__properties_version1.0", (isRoot ? burnPropertyStreamHeader32 : burnPropertyStreamHeader24)(recipientsCount, attachmentsCount, props1));
}
}
async function convertGeneric(entries, parentIndex, properties, subNode, resolveHeap, rowIndex) {
const props1 = [];
for (let y = 0; y < properties.length; y++) {
const pair = await convertProperty(entries, parentIndex, properties[y], subNode, resolveHeap, false);
props1.push(pair.prop1);
}
{
// At least 1 property are mandatory for contact root:
// - PidTagRowid
const arrayBuffer = new ArrayBuffer(4);
const view = new DataView(arrayBuffer);
view.setUint32(0, rowIndex, true);
props1.push({
tagLo: 0x0003,
tagHi: 0x3000,
mandatory: false,
readable: true,
writeable: true,
value: new Uint8Array(arrayBuffer, 0, 4),
});
}
addFile(entries, parentIndex, "__properties_version1.0", burnPropertyStreamHeader8(props1));
}
async function convertAtt(entries, parentIndex, properties, subNode, resolveHeap, attachmentIndex) {
const props1 = [];
for (let y = 0; y < properties.length; y++) {
const pair = await convertProperty(entries, parentIndex, properties[y], subNode, resolveHeap, false);
props1.push(pair.prop1);
}
{
// At least 2 properties are mandatory for attachment root:
// - PidTagAttachNumber
// - PidTagAttachMethod (also included in pst)
const arrayBuffer = new ArrayBuffer(4);
const view = new DataView(arrayBuffer);
view.setUint32(0, attachmentIndex, true);
props1.push({
tagLo: 0x0003,
tagHi: 0x0e21,
mandatory: false,
readable: true,
writeable: false,
value: new Uint8Array(arrayBuffer, 0, 4),
});
}
addFile(entries, parentIndex, "__properties_version1.0", burnPropertyStreamHeader8(props1));
}
async function convertNameId(entries, parentIndex, properties, subNode, resolveHeap) {
const props1 = [];
for (let y = 0; y < properties.length; y++) {
const pair = await convertProperty(entries, parentIndex, properties[y], subNode, resolveHeap, true);
props1.push(pair.prop1);
}
}
function addFile(entries, parentIndex, name, data) {
const entry = {
name,
type: Reader_1.TypeEnum.DOCUMENT,
length: data.length,
binaryProvider: () => data,
};
const fileIndex = entries.length;
entries.push(entry);
entries[parentIndex].children = (entries[parentIndex].children || []).concat(fileIndex);
}
function addDir(entries, parentIndex, name) {
const entry = {
name,
type: Reader_1.TypeEnum.DIRECTORY,
children: [],
length: 0,
};
const newIndex = entries.length;
entries.push(entry);
entries[parentIndex].children = (entries[parentIndex].children || []).concat(newIndex);
return newIndex;
}
function burnPropertyStreamHeader32(recipientsCount, attachmentsCount, props) {
const array = new Uint8Array(32 + 16 * props.length);
const view = new DataView(array.buffer);
view.setUint32(0x08, recipientsCount, true); // int NextRecipientID
view.setUint32(0x0c, attachmentsCount, true); // int NextAttachmentID
view.setUint32(0x10, recipientsCount, true); // int RecipientCount
view.setUint32(0x14, attachmentsCount, true); // int AttachmentCount
for (let y = 0; y < props.length; y++) {
const top = 32 + 16 * y;
const one = props[y];
view.setUint16(top + 0x0, one.tagLo, true);
view.setUint16(top + 0x2, one.tagHi, true);
view.setUint32(top + 0x4, (one.mandatory ? 1 : 0) |
(one.readable ? 2 : 0) |
(one.writeable ? 4 : 0), true);
if (8 < one.value.length) {
throw new Error("Property value has to be at most 8 bytes long!");
}
array.set(one.value, top + 0x8);
}
return array;
}
function burnPropertyStreamHeader24(recipientsCount, attachmentsCount, props) {
const array = new Uint8Array(24 + 16 * props.length);
const view = new DataView(array.buffer);
view.setUint32(0x08, recipientsCount, true); // int NextRecipientID
view.setUint32(0x0c, attachmentsCount, true); // int NextAttachmentID
view.setUint32(0x10, recipientsCount, true); // int RecipientCount
view.setUint32(0x14, attachmentsCount, true); // int AttachmentCount
for (let y = 0; y < props.length; y++) {
const top = 24 + 16 * y;
const one = props[y];
view.setUint16(top + 0x0, one.tagLo, true);
view.setUint16(top + 0x2, one.tagHi, true);
view.setUint32(top + 0x4, (one.mandatory ? 1 : 0) |
(one.readable ? 2 : 0) |
(one.writeable ? 4 : 0), true);
if (8 < one.value.length) {
throw new Error("Property value has to be at most 8 bytes long!");
}
array.set(one.value, top + 0x8);
}
return array;
}
function burnPropertyStreamHeader8(props) {
const array = new Uint8Array(8 + 16 * props.length);
const view = new DataView(array.buffer);
for (let y = 0; y < props.length; y++) {
const top = 8 + 16 * y;
const one = props[y];
view.setUint16(top + 0x0, one.tagLo, true);
view.setUint16(top + 0x2, one.tagHi, true);
view.setUint32(top + 0x4, (one.mandatory ? 1 : 0) |
(one.readable ? 2 : 0) |
(one.writeable ? 4 : 0), true);
if (8 < one.value.length) {
throw new Error("Property value has to be at most 8 bytes long!");
}
array.set(one.value, top + 0x8);
}
return array;
}
async function convertProperty(entries, parentIndex, prop, subNode, resolveHeap, writeAllPropsToFile) {
let value = prop.value;
// console.log("convertProperty", toHex2(prop.key), toHex2(prop.type), prop.value?.byteLength, prop.value);
if (false ||
prop.type === 0x000b ||
prop.type === 0x0002 ||
prop.type === 0x0004 ||
prop.type === 0x0003) {
if (writeAllPropsToFile) {
if (value) {
addFile(entries, parentIndex, `__substg1.0_${(0, utils_1.toHex2)(prop.key, true)}${(0, utils_1.toHex2)(prop.type, true)}`, new Uint8Array(value));
}
}
}
else if (prop.type === 0x000d) {
// `__substg1.0_3701000D`
const dirIndex = addDir(entries, parentIndex, `__substg1.0_${(0, utils_1.toHex2)(prop.key, true)}000D`);
const hnid = new DataView(value).getUint32(0, true);
const bytes = await resolveHeap(hnid);
if (bytes !== undefined) {
const view = new DataView(bytes);
const subNodeId = view.getUint32(0, true);
const subSubNode = await subNode.getSubNodeOf(subNodeId);
if (subSubNode) {
await convertMessage(entries, dirIndex, subSubNode, false, undefined);
value = new ArrayBuffer(8);
const view = new DataView(value);
view.setUint32(0, 0, true);
view.setUint32(4, 1, true); // ATTACH_EMBEDDED_MSG (0x00000005) → 0x01
}
}
}
else {
if (value && 4 <= value.byteLength) {
const hnid = new DataView(value).getUint32(0, true);
const bytes = await resolveHeap(hnid);
if (bytes !== undefined) {
const apply = (actualData) => {
addFile(entries, parentIndex, `__substg1.0_${(0, utils_1.toHex2)(prop.key, true)}${(0, utils_1.toHex2)(prop.type, true)}`, new Uint8Array(actualData));
};
if (false) {
}
else if (prop.type === 0x1e) {
const altBytes = fixAnsiString(bytes);
value = new ArrayBuffer(8);
const view = new DataView(value);
view.setUint32(0, altBytes.byteLength + 1, true);
apply(altBytes);
}
else if (prop.type === 0x1f) {
const altBytes = fixUnicodeString(bytes);
value = new ArrayBuffer(8);
const view = new DataView(value);
view.setUint32(0, altBytes.byteLength + 2, true);
apply(altBytes);
}
else {
value = new ArrayBuffer(8);
const view = new DataView(value);
view.setUint32(0, bytes.byteLength, true);
apply(bytes);
}
}
else {
addFile(entries, parentIndex, `__substg1.0_${(0, utils_1.toHex2)(prop.key, true)}${(0, utils_1.toHex2)(prop.type, true)}`, new Uint8Array(0));
}
}
else {
addFile(entries, parentIndex, `__substg1.0_${(0, utils_1.toHex2)(prop.key, true)}${(0, utils_1.toHex2)(prop.type, true)}`, new Uint8Array(0));
}
}
return {
prop1: {
tagLo: prop.type,
tagHi: prop.key,
mandatory: false,
readable: true,
writeable: true,
value: value ? new Uint8Array(value) : new Uint8Array(0),
},
};
}
function fixUnicodeString(bytes) {
if (4 <= bytes.byteLength) {
const view = new Uint8Array(bytes);
return view[0] === 1 && view[1] === 0 && view[2] === 1 && view[3] === 0
? bytes.slice(4)
: bytes;
}
else {
return bytes;
}
}
function fixAnsiString(bytes) {
if (2 <= bytes.byteLength) {
const view = new Uint8Array(bytes);
return view[0] === 1 && view[1] === 1 ? bytes.slice(2) : bytes;
}
else {
return bytes;
}
}
//# sourceMappingURL=convertToMsg.js.map