diff --git a/index.js b/index.js new file mode 100644 index 0000000..2b5bdf2 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +import DataStream from './DataStream' +import MsgReader from './msg.reader' +import MultipartForm from './multipart.form' + +export default { + MsgReader, + DataStream, + MultipartForm +} diff --git a/msg.reader.js b/msg.reader.js index e68d072..b21ef9b 100644 --- a/msg.reader.js +++ b/msg.reader.js @@ -16,535 +16,549 @@ MSG Reader */ -(function () { +if (!DataStream) { + const DataStream = require('./DataStream'); +} - // constants - var CONST = { - FILE_HEADER: uInt2int([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1]), - MSG: { - UNUSED_BLOCK: -1, - END_OF_CHAIN: -2, +// constants +const CONST = { + FILE_HEADER: uInt2int([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1]), + MSG: { + UNUSED_BLOCK: -1, + END_OF_CHAIN: -2, - S_BIG_BLOCK_SIZE: 0x0200, - S_BIG_BLOCK_MARK: 9, + S_BIG_BLOCK_SIZE: 0x0200, + S_BIG_BLOCK_MARK: 9, - L_BIG_BLOCK_SIZE: 0x1000, - L_BIG_BLOCK_MARK: 12, + L_BIG_BLOCK_SIZE: 0x1000, + L_BIG_BLOCK_MARK: 12, - SMALL_BLOCK_SIZE: 0x0040, - BIG_BLOCK_MIN_DOC_SIZE: 0x1000, - HEADER: { - PROPERTY_START_OFFSET: 0x30, + SMALL_BLOCK_SIZE: 0x0040, + BIG_BLOCK_MIN_DOC_SIZE: 0x1000, + HEADER: { + PROPERTY_START_OFFSET: 0x30, - BAT_START_OFFSET: 0x4c, - BAT_COUNT_OFFSET: 0x2C, + BAT_START_OFFSET: 0x4c, + BAT_COUNT_OFFSET: 0x2C, - SBAT_START_OFFSET: 0x3C, - SBAT_COUNT_OFFSET: 0x40, + SBAT_START_OFFSET: 0x3C, + SBAT_COUNT_OFFSET: 0x40, - XBAT_START_OFFSET: 0x44, - XBAT_COUNT_OFFSET: 0x48 + XBAT_START_OFFSET: 0x44, + XBAT_COUNT_OFFSET: 0x48 + }, + PROP: { + NO_INDEX: -1, + PROPERTY_SIZE: 0x0080, + + NAME_SIZE_OFFSET: 0x40, + MAX_NAME_LENGTH: (/*NAME_SIZE_OFFSET*/0x40 / 2) - 1, + TYPE_OFFSET: 0x42, + PREVIOUS_PROPERTY_OFFSET: 0x44, + NEXT_PROPERTY_OFFSET: 0x48, + CHILD_PROPERTY_OFFSET: 0x4C, + START_BLOCK_OFFSET: 0x74, + SIZE_OFFSET: 0x78, + TYPE_ENUM: { + DIRECTORY: 1, + DOCUMENT: 2, + ROOT: 5 + } + }, + FIELD: { + PREFIX: { + ATTACHMENT: '__attach_version1.0', + RECIPIENT: '__recip_version1.0', + DOCUMENT: '__substg1.' }, - PROP: { - NO_INDEX: -1, - PROPERTY_SIZE: 0x0080, - - NAME_SIZE_OFFSET: 0x40, - MAX_NAME_LENGTH: (/*NAME_SIZE_OFFSET*/0x40 / 2) - 1, - TYPE_OFFSET: 0x42, - PREVIOUS_PROPERTY_OFFSET: 0x44, - NEXT_PROPERTY_OFFSET: 0x48, - CHILD_PROPERTY_OFFSET: 0x4C, - START_BLOCK_OFFSET: 0x74, - SIZE_OFFSET: 0x78, - TYPE_ENUM: { - DIRECTORY: 1, - DOCUMENT: 2, - ROOT: 5 - } + // example (use fields as needed) + NAME_MAPPING: { + // email specific + '0037': 'subject', + '0c1a': 'senderName', + '5d02': 'senderEmail', + '1000': 'body', + '007d': 'headers', + // attachment specific + '3703': 'extension', + '3704': 'fileNameShort', + '3707': 'fileName', + '3712': 'pidContentId', + // recipient specific + '3001': 'name', + '39fe': 'email' }, - FIELD: { - PREFIX: { - ATTACHMENT: '__attach_version1.0', - RECIPIENT: '__recip_version1.0', - DOCUMENT: '__substg1.' - }, - // example (use fields as needed) - NAME_MAPPING: { - // email specific - '0037': 'subject', - '0c1a': 'senderName', - '5d02': 'senderEmail', - '1000': 'body', - '007d': 'headers', - // attachment specific - '3703': 'extension', - '3704': 'fileNameShort', - '3707': 'fileName', - '3712': 'pidContentId', - // recipient specific - '3001': 'name', - '39fe': 'email' - }, - CLASS_MAPPING: { - ATTACHMENT_DATA: '3701' - }, - TYPE_MAPPING: { - '001e': 'string', - '001f': 'unicode', - '0102': 'binary' - }, - DIR_TYPE: { - INNER_MSG: '000d' - } + CLASS_MAPPING: { + ATTACHMENT_DATA: '3701' + }, + TYPE_MAPPING: { + '001e': 'string', + '001f': 'unicode', + '0102': 'binary' + }, + DIR_TYPE: { + INNER_MSG: '000d' } } - }; - - // unit utils - function arraysEqual(a, b) { - if (a === b) return true; - if (a == null || b == null) return false; - if (a.length != b.length) return false; - - for (var i = 0; i < a.length; i++) { - if (a[i] !== b[i]) return false; - } - return true; - } - - function uInt2int(data) { - var result = new Array(data.length); - for (var i = 0; i < data.length; i++) { - result[i] = data[i] << 24 >> 24; - } - return result; } +}; - // MSG Reader implementation +// unit utils +function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; - // check MSG file header - function isMSGFile(ds) { - ds.seek(0); - return arraysEqual(CONST.FILE_HEADER, ds.readInt8Array(CONST.FILE_HEADER.length)); + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) return false; } + return true; +} - // FAT utils - function getBlockOffsetAt(msgData, offset) { - return (offset + 1) * msgData.bigBlockSize; +function uInt2int(data) { + const result = new Array(data.length); + for (let i = 0; i < data.length; i++) { + result[i] = data[i] << 24 >> 24; } - - function getBlockAt(ds, msgData, offset) { - var startOffset = getBlockOffsetAt(msgData, offset); - ds.seek(startOffset); - return ds.readInt32Array(msgData.bigBlockLength); + return result; +} + +// MSG Reader implementation + +// check MSG file header +function isMSGFile(ds) { + ds.seek(0); + return arraysEqual(CONST.FILE_HEADER, ds.readInt8Array(CONST.FILE_HEADER.length)); +} + +// FAT utils +function getBlockOffsetAt(msgData, offset) { + return (offset + 1) * msgData.bigBlockSize; +} + +function getBlockAt(ds, msgData, offset) { + let startOffset = getBlockOffsetAt(msgData, offset); + ds.seek(startOffset); + return ds.readInt32Array(msgData.bigBlockLength); +} + +function getNextBlockInner(ds, msgData, offset, blockOffsetData) { + let currentBlock = Math.floor(offset / msgData.bigBlockLength); + let currentBlockIndex = offset % msgData.bigBlockLength; + + let startBlockOffset = blockOffsetData[currentBlock]; + + return getBlockAt(ds, msgData, startBlockOffset)[currentBlockIndex]; +} + +function getNextBlock(ds, msgData, offset) { + return getNextBlockInner(ds, msgData, offset, msgData.batData); +} + +function getNextBlockSmall(ds, msgData, offset) { + return getNextBlockInner(ds, msgData, offset, msgData.sbatData); +} + +// convert binary data to dictionary +function parseMsgData(ds) { + let msgData = headerData(ds); + msgData.batData = batData(ds, msgData); + msgData.sbatData = sbatData(ds, msgData); + if (msgData.xbatCount > 0) { + xbatData(ds, msgData); } - - function getNextBlockInner(ds, msgData, offset, blockOffsetData) { - var currentBlock = Math.floor(offset / msgData.bigBlockLength); - var currentBlockIndex = offset % msgData.bigBlockLength; - - var startBlockOffset = blockOffsetData[currentBlock]; - - return getBlockAt(ds, msgData, startBlockOffset)[currentBlockIndex]; + msgData.propertyData = propertyData(ds, msgData); + msgData.fieldsData = fieldsData(ds, msgData); + + return msgData; +} + +// extract header data +function headerData(ds) { + let headerData = {}; + + // system data + headerData.bigBlockSize = + ds.readByte(/*const position*/30) == CONST.MSG.L_BIG_BLOCK_MARK ? CONST.MSG.L_BIG_BLOCK_SIZE : CONST.MSG.S_BIG_BLOCK_SIZE; + headerData.bigBlockLength = headerData.bigBlockSize / 4; + headerData.xBlockLength = headerData.bigBlockLength - 1; + + // header data + headerData.batCount = ds.readInt(CONST.MSG.HEADER.BAT_COUNT_OFFSET); + headerData.propertyStart = ds.readInt(CONST.MSG.HEADER.PROPERTY_START_OFFSET); + headerData.sbatStart = ds.readInt(CONST.MSG.HEADER.SBAT_START_OFFSET); + headerData.sbatCount = ds.readInt(CONST.MSG.HEADER.SBAT_COUNT_OFFSET); + headerData.xbatStart = ds.readInt(CONST.MSG.HEADER.XBAT_START_OFFSET); + headerData.xbatCount = ds.readInt(CONST.MSG.HEADER.XBAT_COUNT_OFFSET); + + return headerData; +} + +function batCountInHeader(msgData) { + let maxBatsInHeader = (CONST.MSG.S_BIG_BLOCK_SIZE - CONST.MSG.HEADER.BAT_START_OFFSET) / 4; + return Math.min(msgData.batCount, maxBatsInHeader); +} + +function batData(ds, msgData) { + let result = new Array(batCountInHeader(msgData)); + ds.seek(CONST.MSG.HEADER.BAT_START_OFFSET); + for (let i = 0; i < result.length; i++) { + result[i] = ds.readInt32() } + return result; +} - function getNextBlock(ds, msgData, offset) { - return getNextBlockInner(ds, msgData, offset, msgData.batData); - } +function sbatData(ds, msgData) { + let result = []; + let startIndex = msgData.sbatStart; - function getNextBlockSmall(ds, msgData, offset) { - return getNextBlockInner(ds, msgData, offset, msgData.sbatData); + for (let i = 0; i < msgData.sbatCount && startIndex != CONST.MSG.END_OF_CHAIN; i++) { + result.push(startIndex); + startIndex = getNextBlock(ds, msgData, startIndex); } - - // convert binary data to dictionary - function parseMsgData(ds) { - var msgData = headerData(ds); - msgData.batData = batData(ds, msgData); - msgData.sbatData = sbatData(ds, msgData); - if (msgData.xbatCount > 0) { - xbatData(ds, msgData); + return result; +} + +function xbatData(ds, msgData) { + let batCount = batCountInHeader(msgData); + let batCountTotal = msgData.batCount; + let remainingBlocks = batCountTotal - batCount; + + let nextBlockAt = msgData.xbatStart; + for (let i = 0; i < msgData.xbatCount; i++) { + let xBatBlock = getBlockAt(ds, msgData, nextBlockAt); + nextBlockAt = xBatBlock[msgData.xBlockLength]; + + let blocksToProcess = Math.min(remainingBlocks, msgData.xBlockLength); + for (let j = 0; j < blocksToProcess; j++) { + let blockStartAt = xBatBlock[j]; + if (blockStartAt == CONST.MSG.UNUSED_BLOCK || blockStartAt == CONST.MSG.END_OF_CHAIN) { + break; + } + msgData.batData.push(blockStartAt); } - msgData.propertyData = propertyData(ds, msgData); - msgData.fieldsData = fieldsData(ds, msgData); - - return msgData; + remainingBlocks -= blocksToProcess; } +} - // extract header data - function headerData(ds) { - var headerData = {}; - - // system data - headerData.bigBlockSize = - ds.readByte(/*const position*/30) == CONST.MSG.L_BIG_BLOCK_MARK ? CONST.MSG.L_BIG_BLOCK_SIZE : CONST.MSG.S_BIG_BLOCK_SIZE; - headerData.bigBlockLength = headerData.bigBlockSize / 4; - headerData.xBlockLength = headerData.bigBlockLength - 1; - - // header data - headerData.batCount = ds.readInt(CONST.MSG.HEADER.BAT_COUNT_OFFSET); - headerData.propertyStart = ds.readInt(CONST.MSG.HEADER.PROPERTY_START_OFFSET); - headerData.sbatStart = ds.readInt(CONST.MSG.HEADER.SBAT_START_OFFSET); - headerData.sbatCount = ds.readInt(CONST.MSG.HEADER.SBAT_COUNT_OFFSET); - headerData.xbatStart = ds.readInt(CONST.MSG.HEADER.XBAT_START_OFFSET); - headerData.xbatCount = ds.readInt(CONST.MSG.HEADER.XBAT_COUNT_OFFSET); - - return headerData; - } +// extract property data and property hierarchy +function propertyData(ds, msgData) { + let props = []; - function batCountInHeader(msgData) { - var maxBatsInHeader = (CONST.MSG.S_BIG_BLOCK_SIZE - CONST.MSG.HEADER.BAT_START_OFFSET) / 4; - return Math.min(msgData.batCount, maxBatsInHeader); - } + let currentOffset = msgData.propertyStart; - function batData(ds, msgData) { - var result = new Array(batCountInHeader(msgData)); - ds.seek(CONST.MSG.HEADER.BAT_START_OFFSET); - for (var i = 0; i < result.length; i++) { - result[i] = ds.readInt32() - } - return result; + while (currentOffset != CONST.MSG.END_OF_CHAIN) { + convertBlockToProperties(ds, msgData, currentOffset, props); + currentOffset = getNextBlock(ds, msgData, currentOffset); } - - function sbatData(ds, msgData) { - var result = []; - var startIndex = msgData.sbatStart; - - for (var i = 0; i < msgData.sbatCount && startIndex != CONST.MSG.END_OF_CHAIN; i++) { - result.push(startIndex); - startIndex = getNextBlock(ds, msgData, startIndex); - } - return result; + createPropertyHierarchy(props, /*property with index 0 (zero) always as root*/props[0]); + return props; +} + +function convertName(ds, offset) { + let nameLength = ds.readShort(offset + CONST.MSG.PROP.NAME_SIZE_OFFSET); + if (nameLength < 1) { + return ''; + } else { + return ds.readStringAt(offset, nameLength / 2); } - - function xbatData(ds, msgData) { - var batCount = batCountInHeader(msgData); - var batCountTotal = msgData.batCount; - var remainingBlocks = batCountTotal - batCount; - - var nextBlockAt = msgData.xbatStart; - for (var i = 0; i < msgData.xbatCount; i++) { - var xBatBlock = getBlockAt(ds, msgData, nextBlockAt); - nextBlockAt = xBatBlock[msgData.xBlockLength]; - - var blocksToProcess = Math.min(remainingBlocks, msgData.xBlockLength); - for (var j = 0; j < blocksToProcess; j++) { - var blockStartAt = xBatBlock[j]; - if (blockStartAt == CONST.MSG.UNUSED_BLOCK || blockStartAt == CONST.MSG.END_OF_CHAIN) { - break; - } - msgData.batData.push(blockStartAt); - } - remainingBlocks -= blocksToProcess; +} + +function convertProperty(ds, index, offset) { + return { + index: index, + type: ds.readByte(offset + CONST.MSG.PROP.TYPE_OFFSET), + name: convertName(ds, offset), + // hierarchy + previousProperty: ds.readInt(offset + CONST.MSG.PROP.PREVIOUS_PROPERTY_OFFSET), + nextProperty: ds.readInt(offset + CONST.MSG.PROP.NEXT_PROPERTY_OFFSET), + childProperty: ds.readInt(offset + CONST.MSG.PROP.CHILD_PROPERTY_OFFSET), + // data offset + startBlock: ds.readInt(offset + CONST.MSG.PROP.START_BLOCK_OFFSET), + sizeBlock: ds.readInt(offset + CONST.MSG.PROP.SIZE_OFFSET) + }; +} + +function convertBlockToProperties(ds, msgData, propertyBlockOffset, props) { + + let propertyCount = msgData.bigBlockSize / CONST.MSG.PROP.PROPERTY_SIZE; + let propertyOffset = getBlockOffsetAt(msgData, propertyBlockOffset); + + for (let i = 0; i < propertyCount; i++) { + let propertyType = ds.readByte(propertyOffset + CONST.MSG.PROP.TYPE_OFFSET); + switch (propertyType) { + case CONST.MSG.PROP.TYPE_ENUM.ROOT: + case CONST.MSG.PROP.TYPE_ENUM.DIRECTORY: + case CONST.MSG.PROP.TYPE_ENUM.DOCUMENT: + props.push(convertProperty(ds, props.length, propertyOffset)); + break; + default: + /* unknown property types */ + props.push(null); } - } - - // extract property data and property hierarchy - function propertyData(ds, msgData) { - var props = []; - var currentOffset = msgData.propertyStart; - - while (currentOffset != CONST.MSG.END_OF_CHAIN) { - convertBlockToProperties(ds, msgData, currentOffset, props); - currentOffset = getNextBlock(ds, msgData, currentOffset); - } - createPropertyHierarchy(props, /*property with index 0 (zero) always as root*/props[0]); - return props; + propertyOffset += CONST.MSG.PROP.PROPERTY_SIZE; } +} - function convertName(ds, offset) { - var nameLength = ds.readShort(offset + CONST.MSG.PROP.NAME_SIZE_OFFSET); - if (nameLength < 1) { - return ''; - } else { - return ds.readStringAt(offset, nameLength / 2); - } - } +function createPropertyHierarchy(props, nodeProperty) { - function convertProperty(ds, index, offset) { - return { - index: index, - type: ds.readByte(offset + CONST.MSG.PROP.TYPE_OFFSET), - name: convertName(ds, offset), - // hierarchy - previousProperty: ds.readInt(offset + CONST.MSG.PROP.PREVIOUS_PROPERTY_OFFSET), - nextProperty: ds.readInt(offset + CONST.MSG.PROP.NEXT_PROPERTY_OFFSET), - childProperty: ds.readInt(offset + CONST.MSG.PROP.CHILD_PROPERTY_OFFSET), - // data offset - startBlock: ds.readInt(offset + CONST.MSG.PROP.START_BLOCK_OFFSET), - sizeBlock: ds.readInt(offset + CONST.MSG.PROP.SIZE_OFFSET) - }; + if (nodeProperty.childProperty == CONST.MSG.PROP.NO_INDEX) { + return; } - - function convertBlockToProperties(ds, msgData, propertyBlockOffset, props) { - - var propertyCount = msgData.bigBlockSize / CONST.MSG.PROP.PROPERTY_SIZE; - var propertyOffset = getBlockOffsetAt(msgData, propertyBlockOffset); - - for (var i = 0; i < propertyCount; i++) { - var propertyType = ds.readByte(propertyOffset + CONST.MSG.PROP.TYPE_OFFSET); - switch (propertyType) { - case CONST.MSG.PROP.TYPE_ENUM.ROOT: - case CONST.MSG.PROP.TYPE_ENUM.DIRECTORY: - case CONST.MSG.PROP.TYPE_ENUM.DOCUMENT: - props.push(convertProperty(ds, props.length, propertyOffset)); - break; - default: - /* unknown property types */ - props.push(null); - } - - propertyOffset += CONST.MSG.PROP.PROPERTY_SIZE; + nodeProperty.children = []; + + let children = [nodeProperty.childProperty]; + while (children.length != 0) { + let currentIndex = children.shift(); + let current = props[currentIndex]; + if (current == null) { + continue; } - } - - function createPropertyHierarchy(props, nodeProperty) { + nodeProperty.children.push(currentIndex); - if (nodeProperty.childProperty == CONST.MSG.PROP.NO_INDEX) { - return; + if (current.type == CONST.MSG.PROP.TYPE_ENUM.DIRECTORY) { + createPropertyHierarchy(props, current); } - nodeProperty.children = []; - - var children = [nodeProperty.childProperty]; - while (children.length != 0) { - var currentIndex = children.shift(); - var current = props[currentIndex]; - if (current == null) { - continue; - } - nodeProperty.children.push(currentIndex); - - if (current.type == CONST.MSG.PROP.TYPE_ENUM.DIRECTORY) { - createPropertyHierarchy(props, current); - } - if (current.previousProperty != CONST.MSG.PROP.NO_INDEX) { - children.push(current.previousProperty); - } - if (current.nextProperty != CONST.MSG.PROP.NO_INDEX) { - children.push(current.nextProperty); - } + if (current.previousProperty != CONST.MSG.PROP.NO_INDEX) { + children.push(current.previousProperty); + } + if (current.nextProperty != CONST.MSG.PROP.NO_INDEX) { + children.push(current.nextProperty); } } +} - // extract real fields - function fieldsData(ds, msgData) { - var fields = { - attachments: [], - recipients: [] - }; - fieldsDataDir(ds, msgData, msgData.propertyData[0], fields); - return fields; - } +// extract real fields +function fieldsData(ds, msgData) { + let fields = { + attachments: [], + recipients: [] + }; + fieldsDataDir(ds, msgData, msgData.propertyData[0], fields); + return fields; +} - function fieldsDataDir(ds, msgData, dirProperty, fields) { +function fieldsDataDir(ds, msgData, dirProperty, fields) { - if (dirProperty.children && dirProperty.children.length > 0) { - for (var i = 0; i < dirProperty.children.length; i++) { - var childProperty = msgData.propertyData[dirProperty.children[i]]; + if (dirProperty.children && dirProperty.children.length > 0) { + for (let i = 0; i < dirProperty.children.length; i++) { + let childProperty = msgData.propertyData[dirProperty.children[i]]; - if (childProperty.type == CONST.MSG.PROP.TYPE_ENUM.DIRECTORY) { - fieldsDataDirInner(ds, msgData, childProperty, fields) - } else if (childProperty.type == CONST.MSG.PROP.TYPE_ENUM.DOCUMENT - && childProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.DOCUMENT) == 0) { - fieldsDataDocument(ds, msgData, childProperty, fields); - } + if (childProperty.type == CONST.MSG.PROP.TYPE_ENUM.DIRECTORY) { + fieldsDataDirInner(ds, msgData, childProperty, fields) + } else if (childProperty.type == CONST.MSG.PROP.TYPE_ENUM.DOCUMENT + && childProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.DOCUMENT) == 0) { + fieldsDataDocument(ds, msgData, childProperty, fields); } } } - - function fieldsDataDirInner(ds, msgData, dirProperty, fields) { - if (dirProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.ATTACHMENT) == 0) { - - // attachment - var attachmentField = {}; - fields.attachments.push(attachmentField); - fieldsDataDir(ds, msgData, dirProperty, attachmentField); - } else if (dirProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.RECIPIENT) == 0) { - - // recipient - var recipientField = {}; - fields.recipients.push(recipientField); - fieldsDataDir(ds, msgData, dirProperty, recipientField); +} + +function fieldsDataDirInner(ds, msgData, dirProperty, fields) { + if (dirProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.ATTACHMENT) == 0) { + + // attachment + let attachmentField = {}; + fields.attachments.push(attachmentField); + fieldsDataDir(ds, msgData, dirProperty, attachmentField); + } else if (dirProperty.name.indexOf(CONST.MSG.FIELD.PREFIX.RECIPIENT) == 0) { + + // recipient + let recipientField = {}; + fields.recipients.push(recipientField); + fieldsDataDir(ds, msgData, dirProperty, recipientField); + } else { + + // other dir + let childFieldType = getFieldType(dirProperty); + if (childFieldType != CONST.MSG.FIELD.DIR_TYPE.INNER_MSG) { + fieldsDataDir(ds, msgData, dirProperty, fields); } else { - - // other dir - var childFieldType = getFieldType(dirProperty); - if (childFieldType != CONST.MSG.FIELD.DIR_TYPE.INNER_MSG) { - fieldsDataDir(ds, msgData, dirProperty, fields); - } else { - // MSG as attachment currently isn't supported - fields.innerMsgContent = true; - } + // MSG as attachment currently isn't supported + fields.innerMsgContent = true; } } +} - function isAddPropertyValue(fieldName, fieldTypeMapped) { - return fieldName !== 'body' || fieldTypeMapped !== 'binary'; - } +function isAddPropertyValue(fieldName, fieldTypeMapped) { + return fieldName !== 'body' || fieldTypeMapped !== 'binary'; +} - function fieldsDataDocument(ds, msgData, documentProperty, fields) { - var value = documentProperty.name.substring(12).toLowerCase(); - var fieldClass = value.substring(0, 4); - var fieldType = value.substring(4, 8); +function fieldsDataDocument(ds, msgData, documentProperty, fields) { + let value = documentProperty.name.substring(12).toLowerCase(); + let fieldClass = value.substring(0, 4); + let fieldType = value.substring(4, 8); - var fieldName = CONST.MSG.FIELD.NAME_MAPPING[fieldClass]; - var fieldTypeMapped = CONST.MSG.FIELD.TYPE_MAPPING[fieldType]; + let fieldName = CONST.MSG.FIELD.NAME_MAPPING[fieldClass]; + let fieldTypeMapped = CONST.MSG.FIELD.TYPE_MAPPING[fieldType]; - if (fieldName) { - var fieldValue = getFieldValue(ds, msgData, documentProperty, fieldTypeMapped); + if (fieldName) { + let fieldValue = getFieldValue(ds, msgData, documentProperty, fieldTypeMapped); - if (isAddPropertyValue(fieldName, fieldTypeMapped)) { - fields[fieldName] = fieldValue; - } - } - if (fieldClass == CONST.MSG.FIELD.CLASS_MAPPING.ATTACHMENT_DATA) { - - // attachment specific info - fields['dataId'] = documentProperty.index; - fields['contentLength'] = documentProperty.sizeBlock; + if (isAddPropertyValue(fieldName, fieldTypeMapped)) { + fields[fieldName] = fieldValue; } } + if (fieldClass == CONST.MSG.FIELD.CLASS_MAPPING.ATTACHMENT_DATA) { - function getFieldType(fieldProperty) { - var value = fieldProperty.name.substring(12).toLowerCase(); - return value.substring(4, 8); + // attachment specific info + fields['dataId'] = documentProperty.index; + fields['contentLength'] = documentProperty.sizeBlock; } - - // extractor structure to manage bat/sbat block types and different data types - var extractorFieldValue = { - sbat: { - 'extractor': function extractDataViaSbat(ds, msgData, fieldProperty, dataTypeExtractor) { - var chain = getChainByBlockSmall(ds, msgData, fieldProperty); - if (chain.length == 1) { - return readDataByBlockSmall(ds, msgData, fieldProperty.startBlock, fieldProperty.sizeBlock, dataTypeExtractor); - } else if (chain.length > 1) { - return readChainDataByBlockSmall(ds, msgData, fieldProperty, chain, dataTypeExtractor); - } - return null; - }, - dataType: { - 'string': function extractBatString(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { - ds.seek(blockStartOffset + bigBlockOffset); - return ds.readString(blockSize); - }, - 'unicode': function extractBatUnicode(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { - ds.seek(blockStartOffset + bigBlockOffset); - return ds.readUCS2String(blockSize / 2); - }, - 'binary': function extractBatBinary(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { - ds.seek(blockStartOffset + bigBlockOffset); - return ds.readUint8Array(blockSize); - } +} + +function getFieldType(fieldProperty) { + let value = fieldProperty.name.substring(12).toLowerCase(); + return value.substring(4, 8); +} + +// extractor structure to manage bat/sbat block types and different data types +let extractorFieldValue = { + sbat: { + 'extractor': function extractDataViaSbat(ds, msgData, fieldProperty, dataTypeExtractor) { + let chain = getChainByBlockSmall(ds, msgData, fieldProperty); + if (chain.length == 1) { + return readDataByBlockSmall(ds, msgData, fieldProperty.startBlock, fieldProperty.sizeBlock, dataTypeExtractor); + } else if (chain.length > 1) { + return readChainDataByBlockSmall(ds, msgData, fieldProperty, chain, dataTypeExtractor); } + return null; }, - bat: { - 'extractor': function extractDataViaBat(ds, msgData, fieldProperty, dataTypeExtractor) { - var offset = getBlockOffsetAt(msgData, fieldProperty.startBlock); - ds.seek(offset); - return dataTypeExtractor(ds, fieldProperty); + dataType: { + 'string': function extractBatString(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { + ds.seek(blockStartOffset + bigBlockOffset); + return ds.readString(blockSize); + }, + 'unicode': function extractBatUnicode(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { + ds.seek(blockStartOffset + bigBlockOffset); + return ds.readUCS2String(blockSize / 2); }, - dataType: { - 'string': function extractSbatString(ds, fieldProperty) { - return ds.readString(fieldProperty.sizeBlock); - }, - 'unicode': function extractSbatUnicode(ds, fieldProperty) { - return ds.readUCS2String(fieldProperty.sizeBlock / 2); - }, - 'binary': function extractSbatBinary(ds, fieldProperty) { - return ds.readUint8Array(fieldProperty.sizeBlock); - } + 'binary': function extractBatBinary(ds, msgData, blockStartOffset, bigBlockOffset, blockSize) { + ds.seek(blockStartOffset + bigBlockOffset); + return ds.readUint8Array(blockSize); } } - }; - - function readDataByBlockSmall(ds, msgData, startBlock, blockSize, dataTypeExtractor) { - var byteOffset = startBlock * CONST.MSG.SMALL_BLOCK_SIZE; - var bigBlockNumber = Math.floor(byteOffset / msgData.bigBlockSize); - var bigBlockOffset = byteOffset % msgData.bigBlockSize; - - var rootProp = msgData.propertyData[0]; - - var nextBlock = rootProp.startBlock; - for (var i = 0; i < bigBlockNumber; i++) { - nextBlock = getNextBlock(ds, msgData, nextBlock); + }, + bat: { + 'extractor': function extractDataViaBat(ds, msgData, fieldProperty, dataTypeExtractor) { + let offset = getBlockOffsetAt(msgData, fieldProperty.startBlock); + ds.seek(offset); + return dataTypeExtractor(ds, fieldProperty); + }, + dataType: { + 'string': function extractSbatString(ds, fieldProperty) { + return ds.readString(fieldProperty.sizeBlock); + }, + 'unicode': function extractSbatUnicode(ds, fieldProperty) { + return ds.readUCS2String(fieldProperty.sizeBlock / 2); + }, + 'binary': function extractSbatBinary(ds, fieldProperty) { + return ds.readUint8Array(fieldProperty.sizeBlock); + } } - var blockStartOffset = getBlockOffsetAt(msgData, nextBlock); - - return dataTypeExtractor(ds, msgData, blockStartOffset, bigBlockOffset, blockSize); } +}; - function readChainDataByBlockSmall(ds, msgData, fieldProperty, chain, dataTypeExtractor) { - var resultData = new Int8Array(fieldProperty.sizeBlock); +function readDataByBlockSmall(ds, msgData, startBlock, blockSize, dataTypeExtractor) { + let byteOffset = startBlock * CONST.MSG.SMALL_BLOCK_SIZE; + let bigBlockNumber = Math.floor(byteOffset / msgData.bigBlockSize); + let bigBlockOffset = byteOffset % msgData.bigBlockSize; - for (var i = 0, idx = 0; i < chain.length; i++) { - var data = readDataByBlockSmall(ds, msgData, chain[i], CONST.MSG.SMALL_BLOCK_SIZE, extractorFieldValue.sbat.dataType.binary); - for (var j = 0; j < data.length; j++) { - resultData[idx++] = data[j]; - } - } - var localDs = new DataStream(resultData, 0, DataStream.LITTLE_ENDIAN); - return dataTypeExtractor(localDs, msgData, 0, 0, fieldProperty.sizeBlock); - } + let rootProp = msgData.propertyData[0]; - function getChainByBlockSmall(ds, msgData, fieldProperty) { - var blockChain = []; - var nextBlockSmall = fieldProperty.startBlock; - while (nextBlockSmall != CONST.MSG.END_OF_CHAIN) { - blockChain.push(nextBlockSmall); - nextBlockSmall = getNextBlockSmall(ds, msgData, nextBlockSmall); - } - return blockChain; + let nextBlock = rootProp.startBlock; + for (let i = 0; i < bigBlockNumber; i++) { + nextBlock = getNextBlock(ds, msgData, nextBlock); } + let blockStartOffset = getBlockOffsetAt(msgData, nextBlock); - function getFieldValue(ds, msgData, fieldProperty, typeMapped) { - var value = null; + return dataTypeExtractor(ds, msgData, blockStartOffset, bigBlockOffset, blockSize); +} - var valueExtractor = - fieldProperty.sizeBlock < CONST.MSG.BIG_BLOCK_MIN_DOC_SIZE ? extractorFieldValue.sbat : extractorFieldValue.bat; - var dataTypeExtractor = valueExtractor.dataType[typeMapped]; +function readChainDataByBlockSmall(ds, msgData, fieldProperty, chain, dataTypeExtractor) { + let resultData = new Int8Array(fieldProperty.sizeBlock); - if (dataTypeExtractor) { - value = valueExtractor.extractor(ds, msgData, fieldProperty, dataTypeExtractor); + for (let i = 0, idx = 0; i < chain.length; i++) { + let data = readDataByBlockSmall(ds, msgData, chain[i], CONST.MSG.SMALL_BLOCK_SIZE, extractorFieldValue.sbat.dataType.binary); + for (let j = 0; j < data.length; j++) { + resultData[idx++] = data[j]; } - return value; } + let localDs = new DataStream(resultData, 0, DataStream.LITTLE_ENDIAN); + return dataTypeExtractor(localDs, msgData, 0, 0, fieldProperty.sizeBlock); +} + +function getChainByBlockSmall(ds, msgData, fieldProperty) { + let blockChain = []; + let nextBlockSmall = fieldProperty.startBlock; + while (nextBlockSmall != CONST.MSG.END_OF_CHAIN) { + blockChain.push(nextBlockSmall); + nextBlockSmall = getNextBlockSmall(ds, msgData, nextBlockSmall); + } + return blockChain; +} - // MSG Reader - var MSGReader = function (arrayBuffer) { - this.ds = new DataStream(arrayBuffer, 0, DataStream.LITTLE_ENDIAN); - }; +function getFieldValue(ds, msgData, fieldProperty, typeMapped) { + let value = null; - MSGReader.prototype = { - /** - Converts bytes to fields information + let valueExtractor = + fieldProperty.sizeBlock < CONST.MSG.BIG_BLOCK_MIN_DOC_SIZE ? extractorFieldValue.sbat : extractorFieldValue.bat; + let dataTypeExtractor = valueExtractor.dataType[typeMapped]; - @return {Object} The fields data for MSG file - */ - getFileData: function () { - if (!isMSGFile(this.ds)) { - return {error: 'Unsupported file type!'}; - } - if (this.fileData == null) { - this.fileData = parseMsgData(this.ds); - } - return this.fileData.fieldsData; - }, - /** - Reads an attachment content by key/ID - - @return {Object} The attachment for specific attachment key - */ - getAttachment: function (attach) { - var attachData = typeof attach === 'number' ? this.fileData.fieldsData.attachments[attach] : attach; - var fieldProperty = this.fileData.propertyData[attachData.dataId]; - var fieldTypeMapped = CONST.MSG.FIELD.TYPE_MAPPING[getFieldType(fieldProperty)]; - var fieldData = getFieldValue(this.ds, this.fileData, fieldProperty, fieldTypeMapped); - - return {fileName: attachData.fileName, content: fieldData}; + if (dataTypeExtractor) { + value = valueExtractor.extractor(ds, msgData, fieldProperty, dataTypeExtractor); + } + return value; +} + +// MSG Reader +let MSGReader = function (arrayBuffer) { + this.ds = new DataStream(arrayBuffer, 0, DataStream.LITTLE_ENDIAN); +}; + +MSGReader.prototype = { + /** + Converts bytes to fields information + + @return {Object} The fields data for MSG file + */ + getFileData: function () { + if (!isMSGFile(this.ds)) { + return {error: 'Unsupported file type!'}; } - }; - - window.MSGReader = MSGReader; - -})(); + if (this.fileData == null) { + this.fileData = parseMsgData(this.ds); + } + return this.fileData.fieldsData; + }, + /** + Reads an attachment content by key/ID + + @return {Object} The attachment for specific attachment key + */ + getAttachment: function (attach) { + let attachData = typeof attach === 'number' ? this.fileData.fieldsData.attachments[attach] : attach; + let fieldProperty = this.fileData.propertyData[attachData.dataId]; + let fieldTypeMapped = CONST.MSG.FIELD.TYPE_MAPPING[getFieldType(fieldProperty)]; + let fieldData = getFieldValue(this.ds, this.fileData, fieldProperty, fieldTypeMapped); + + return {fileName: attachData.fileName, content: fieldData}; + } +}; + +// Export MSGReader for amd environments +if (typeof define === 'function' && define.amd) { + define('MSGReader', [], function() { + return MSGReader; + }); +} + +// Export MSGReader for CommonJS +if (typeof module === 'object' && module && module.exports) { + module.exports = MSGReader; +} + +if (window) { + window.MSGReader = MSGReader +} diff --git a/multipart.form.js b/multipart.form.js index 2da8712..1bf9c5e 100644 --- a/multipart.form.js +++ b/multipart.form.js @@ -29,98 +29,109 @@ * mf.send({someData: 'Test data'}, [attachment]); */ -(function () { +var CRLF = "\r\n"; - var CRLF = "\r\n"; - - function toBinary(value) { - var nBytes = value.length; - var ui8Data = new Uint8Array(nBytes); - for (var nIdx = 0; nIdx < nBytes; nIdx++) { - ui8Data[nIdx] = value.charCodeAt(nIdx) & 0xff; - } - return ui8Data; - } - - function createBoundary() { - return "--XHR----------------------" + (new Date()).getTime(); +function toBinary(value) { + var nBytes = value.length; + var ui8Data = new Uint8Array(nBytes); + for (var nIdx = 0; nIdx < nBytes; nIdx++) { + ui8Data[nIdx] = value.charCodeAt(nIdx) & 0xff; } - - function createFieldsData(boundary, fieldsData) { - var data = ''; - for (var fieldName in fieldsData) { - if (!fieldsData.hasOwnProperty(fieldName)) { - continue; - } - data += '--' + boundary + CRLF + - 'Content-Disposition: form-data; name="' + fieldName + '"' + CRLF + CRLF + fieldsData[fieldName] + CRLF; + return ui8Data; +} + +function createBoundary() { + return "--XHR----------------------" + (new Date()).getTime(); +} + +function createFieldsData(boundary, fieldsData) { + var data = ''; + for (var fieldName in fieldsData) { + if (!fieldsData.hasOwnProperty(fieldName)) { + continue; } - return data; + data += '--' + boundary + CRLF + + 'Content-Disposition: form-data; name="' + fieldName + '"' + CRLF + CRLF + fieldsData[fieldName] + CRLF; } - - function createFileHeader(boundary, fileName) { - return '--' + boundary + CRLF + - 'Content-Disposition: form-data; name="file"; filename="' + encodeURIComponent(fileName) + '"' + CRLF + - 'Content-Type: application/octet-stream' + CRLF + CRLF + return data; +} + +function createFileHeader(boundary, fileName) { + return '--' + boundary + CRLF + + 'Content-Disposition: form-data; name="file"; filename="' + encodeURIComponent(fileName) + '"' + CRLF + + 'Content-Type: application/octet-stream' + CRLF + CRLF +} + +function createBinaryData(boundary, fieldsData, attachData) { + var binaryData = []; + + binaryData.push(toBinary(createFieldsData(boundary, fieldsData))); + for (var ai = 0; ai < attachData.length; ai++) { + binaryData.push(toBinary(createFileHeader(boundary, attachData[ai].fileName))); + binaryData.push(attachData[ai].content); + binaryData.push(toBinary(CRLF)); } - - function createBinaryData(boundary, fieldsData, attachData) { - var binaryData = []; - - binaryData.push(toBinary(createFieldsData(boundary, fieldsData))); - for (var ai = 0; ai < attachData.length; ai++) { - binaryData.push(toBinary(createFileHeader(boundary, attachData[ai].fileName))); - binaryData.push(attachData[ai].content); - binaryData.push(toBinary(CRLF)); - } - binaryData.push(toBinary('--' + boundary + '--')); - return binaryData; + binaryData.push(toBinary('--' + boundary + '--')); + return binaryData; +} + +function joinBinaryData(binaryData) { + var binaryLength = 0, idx = 0, i = 0, bi = 0; + for (bi = 0; bi < binaryData.length; bi++) { + binaryLength += binaryData[bi].length; } - - function joinBinaryData(binaryData) { - var binaryLength = 0, idx = 0, i = 0, bi = 0; - for (bi = 0; bi < binaryData.length; bi++) { - binaryLength += binaryData[bi].length; + var requestData = new Uint8Array(binaryLength); + for (bi = 0; bi < binaryData.length; bi++) { + for (i = 0; i < binaryData[bi].length; i++) { + requestData[idx++] = binaryData[bi][i]; } - var requestData = new Uint8Array(binaryLength); - for (bi = 0; bi < binaryData.length; bi++) { - for (i = 0; i < binaryData[bi].length; i++) { - requestData[idx++] = binaryData[bi][i]; - } - } - return requestData; - } - - function createRequestData(boundary, fieldsData, attachData) { - var binaryData = createBinaryData(boundary, fieldsData, attachData); - var requestData = joinBinaryData(binaryData); - binaryData = null; - return requestData; } - - // MultipartForm - var MultipartForm = function (uploadUrl, readyStateChangeHandler) { - this.uploadUrl = uploadUrl; - this.readyStateChangeHandler = readyStateChangeHandler; - }; - - MultipartForm.prototype = { - send: function (fieldsData, attachData) { - var context = this; - var xhr = new XMLHttpRequest(); - xhr.open('POST', context.uploadUrl); - if (context.readyStateChangeHandler) { - xhr.onreadystatechange = function () { - context.readyStateChangeHandler(xhr) - }; - } - - var boundary = createBoundary(); - xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); - - xhr.send(createRequestData(boundary, fieldsData, attachData)); + return requestData; +} + +function createRequestData(boundary, fieldsData, attachData) { + var binaryData = createBinaryData(boundary, fieldsData, attachData); + var requestData = joinBinaryData(binaryData); + binaryData = null; + return requestData; +} + +// MultipartForm +var MultipartForm = function (uploadUrl, readyStateChangeHandler) { + this.uploadUrl = uploadUrl; + this.readyStateChangeHandler = readyStateChangeHandler; +}; + +MultipartForm.prototype = { + send: function (fieldsData, attachData) { + var context = this; + var xhr = new XMLHttpRequest(); + xhr.open('POST', context.uploadUrl); + if (context.readyStateChangeHandler) { + xhr.onreadystatechange = function () { + context.readyStateChangeHandler(xhr) + }; } - }; - window.MultipartForm = MultipartForm; -})(); \ No newline at end of file + var boundary = createBoundary(); + xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary=" + boundary); + + xhr.send(createRequestData(boundary, fieldsData, attachData)); + } +}; + +// Export MultipartForm for amd environments +if (typeof define === 'function' && define.amd) { + define('MultipartForm', [], function() { + return MultipartForm; + }); +} + +// Export MultipartForm for CommonJS +if (typeof module === 'object' && module && module.exports) { + module.exports = MultipartForm; +} + +if (window) { + window.MultipartForm = MultipartForm +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c35a6c9 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "msg.reader", + "version": "0.2.1", + "author": { + "name": "ykarpovich" + }, + "main": "index.js" +} \ No newline at end of file