🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

eml-parse-js

Package Overview
Dependencies
Maintainers
1
Versions
42
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eml-parse-js - npm Package Compare versions

Comparing version
1.1.15
to
1.2.0-beta.0
dist/index.cjs.js

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

+2
-6

@@ -1,9 +0,5 @@

/**
* @author superchow
* @emil superchow@live.cn
*/
import { Base64 } from 'js-base64';
import { convert, decode, encode } from './charset';
import { GB2312UTF8, mimeDecode, getBoundary } from './utils';
import { EmailAddress, ParsedEmlJson, ReadedEmlJson, BuildOptions, CallbackFn, OptionOrNull, BoundaryRawData, BoundaryConvertedData } from './interface';
import { EmailAddress, ParsedEmlJson, ReadedEmlJson, Attachment, BuildOptions, CallbackFn, OptionOrNull, BoundaryRawData, BoundaryConvertedData, BoundaryHeaders } from './interface';
/**

@@ -76,2 +72,2 @@ * create a boundary

*/
export { getEmailAddress, toEmailAddress, createBoundary, getBoundary, getCharset, unquoteString, unquotePrintable, mimeDecode, Base64, convert, encode, decode, completeBoundary, parse as parseEml, read as readEml, build as buildEml, GB2312UTF8 as GBKUTF8, };
export { getEmailAddress, toEmailAddress, createBoundary, getBoundary, getCharset, unquoteString, unquotePrintable, mimeDecode, Base64, convert, encode, decode, completeBoundary, ParsedEmlJson, ReadedEmlJson, EmailAddress, Attachment, BoundaryHeaders, parse as parseEml, read as readEml, build as buildEml, GB2312UTF8 as GBKUTF8, };

@@ -79,4 +79,4 @@ export interface KeyValue extends Object {

}
export declare type CallbackFn<T> = (error: any, result?: T) => void;
export declare type OptionOrNull = Options | null;
export type CallbackFn<T> = (error: any, result?: T) => void;
export type OptionOrNull = Options | null;
/**

@@ -83,0 +83,0 @@ * BoundaryRawData

@@ -21,3 +21,3 @@ /**

*/
export declare function isStringOrError(param: any): boolean;
export declare function isStringOrError(param: any): param is string | Error;
/**

@@ -24,0 +24,0 @@ * converting strings from gbk to utf-8

{
"name": "eml-parse-js",
"version": "1.1.15",
"version": "1.2.0-beta.0",
"description": "format EML file in browser env",
"main": "./lib/bundle.umd.js",
"module": "./lib/bundle.esm.js",
"es2015": "./lib/bundle.esm.js",
"esm5": "./lib/bundle.esm.js",
"main": "./dist/index.cjs.js",
"module": "./dist/index.es.js",
"browser": "./dist/index.umd.js",
"typings": "./dist/index.d.ts",

@@ -14,34 +13,21 @@ "types": "./dist/index.d.ts",

"types": "./dist/index.d.ts",
"node": "./lib/bundle.cjs.js",
"require": "./lib/bundle.cjs.js",
"es2015": "./lib/bundle.esm.js",
"default": "./lib/bundle.esm.js"
"node": "./dist/index.cjs.js",
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js",
"default": "./dist/index.es.js"
}
},
"scripts": {
"build": "tsc",
"build:es": "tsc -p ./tsconfig.es2015.json",
"build:umd": "tsc -p ./tsconfig.umd.json",
"build:amd": "tsc -p ./tsconfig.amd.json",
"build:ts": "npm run clean:dist && npm run build && npm run build:es -- --declaration false && npm run build:umd -- --declaration false && npm run build:amd",
"build:rollup": "rollup --config",
"build:publish": "npm run clean:dist && npm run build && npm run build:rollup",
"build": "vite build",
"build:publish": "npm run clean:dist && npm run build",
"clean:dist": "rimraf dist",
"eslint:init": "eslint --init",
"lint:ci": "eslint --ext .ts src/",
"lint": "eslint --fix --ext .ts src/",
"link:lib": "rimraf node_modules/eml-format-js && linklocal",
"lint": "eslint --cache --fix src",
"link:lib": "rimraf node_modules/eml-parse-js && linklocal",
"prettier": "prettier --parser typescript --write src/*ts && npm run lint",
"prepublishOnly": "npm run prettier && npm run build:publish && npm run test",
"test": "mocha --reporter spec",
"ci": "npm install"
"test": "mocha --reporter spec"
},
"files": [
"dist/index.d.ts",
"dist/charset.d.ts",
"dist/utils.d.ts",
"dist/interface.d.ts",
"dist/addressparser.d.ts",
"src",
"lib",
"dist",
"LICENSE"

@@ -69,40 +55,37 @@ ],

{
"name" : "Charlie Harding",
"url" : "https://github.com/c-harding"
"name": "Charlie Harding",
"url": "https://github.com/c-harding"
},
{
"name" : "Pádraig Weeks",
"url" : "https://github.com/pmweeks98"
"name": "Pádraig Weeks",
"url": "https://github.com/pmweeks98"
},
{
"name" : "Thomas Oeser",
"url" : "https://github.com/thomasoeser"
"name": "Thomas Oeser",
"url": "https://github.com/thomasoeser"
},
{
"name" : "Robert Scheinpflug",
"url" : "https://github.com/neversun"
"name": "Robert Scheinpflug",
"url": "https://github.com/neversun"
},
{
"name" : "Bean Q",
"url" : "https://github.com/MQpeng"
"name": "Bean Q",
"url": "https://github.com/MQpeng"
}
],
"devDependencies": {
"@types/node": "^17.0.21",
"@types/ramda": "^0.26.36",
"@typescript-eslint/eslint-plugin": "^2.11.0",
"@typescript-eslint/parser": "^2.11.0",
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.17.0",
"chai": "^4.2.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.1",
"eslint": "^9.17.0",
"globals": "^15.14.0",
"iconv-lite": "^0.6.3",
"linklocal": "^2.8.2",
"lint-staged": "^10.5.3",
"mocha": "^6.2.2",
"prettier": "1.19.1",
"rimraf": "^3.0.0",
"rollup": "^1.27.12",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript": "^1.0.1",
"typescript": "^3.7.3"
"prettier": "^3.4.2",
"rimraf": "^6.0.1",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.3",
"vite-plugin-dts": "^4.3.0"
},

@@ -122,3 +105,4 @@ "dependencies": {

]
}
},
"packageManager": "pnpm@9.15.1"
}
define(['exports', 'js-base64', '@sinonjs/text-encoding'], function (exports, jsBase64, textEncoding) { 'use strict';
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
var encode = function (str, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
return new textEncoding.TextEncoder(fromCharset).encode(str);
};
var arr2str = function (arr) {
var CHUNK_SZ = 0x8000;
var strs = [];
for (var i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ)));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
function decode(buf, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
var charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (var _i = 0, charsets_1 = charsets; _i < charsets_1.length; _i++) {
var _a = charsets_1[_i], charset = _a.charset, fatal = _a.fatal;
try {
return new textEncoding.TextDecoder(charset, { fatal: fatal }).decode(buf);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
var convert = function (data, fromCharset) {
return typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
};
function normalizeCharset(charset) {
if (charset === void 0) { charset = 'utf-8'; }
var match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* Gets the boundary name
* @param contentType - string
*/
function getBoundary(contentType) {
var match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
function getCharsetName(charset) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
function wrap(s, i) {
var a = [];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
function mimeDecode(str, fromCharset) {
if (str === void 0) { str = ''; }
if (fromCharset === void 0) { fromCharset = 'UTF-8'; }
var encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
var buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (var i = 0, len = str.length, bufferPos = 0; i < len; i++) {
var hex = str.substr(i + 1, 2);
var chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
}
else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* converting strings from gbk to utf-8
*/
var GB2312UTF8 = {
Dig2Dec: function (s) {
var retV = 0;
if (s.length == 4) {
for (var i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function (s) {
var retS = '';
var tempS = '';
var ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
var sss = '0123456789ABCDEF';
for (var i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function (n1) {
var s = '';
var n2 = 0;
for (var i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
}
else {
s += '0';
}
}
return s;
},
Str2Hex: function (s) {
var c = '';
var n;
var ss = '0123456789ABCDEF';
var digS = '';
for (var i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function (s1) {
var s = escape(s1);
var sa = s.split('%');
var retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (var i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function (str1) {
var substr = '';
var a = '';
var b = '';
var c = '';
var i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
}
else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
}
else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
var token;
var isGroup = false;
var state = 'text';
var address;
var addresses = [];
var data = {
address: [],
comment: [],
group: [],
text: [],
};
var i;
var len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
}
else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
}
else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
}
else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
}
else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
}
else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
var Tokenizer = /** @class */ (function () {
function Tokenizer(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
Tokenizer.prototype.checkChar = function (chr) {
if (this.escaped) ;
else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
}
else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
};
return Tokenizer;
}());
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
function addressparser(str, options) {
options = options || {};
var tokenizer = new Tokenizer(str);
var tokens = tokenizer.tokenize();
var addresses = [];
var address = [];
var parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
}
else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
var addresses_1 = [];
var walkAddressList_1 = function (list) {
list.forEach(function (address) {
if (address.group) {
return walkAddressList_1(address.group);
}
else {
addresses_1.push(address);
}
});
};
walkAddressList_1(parsedAddresses);
return addresses_1;
}
return parsedAddresses;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
/**
* log for test
*/
var verbose = false;
var defaultCharset = 'utf-8';
/**
* create a boundary
*/
function createBoundary() {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data) {
var email = '';
if (typeof data === 'undefined') ;
else if (typeof data === 'string') {
email = data;
}
else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(function (item) {
var str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(function (a) { return a; })
.join(', ');
}
else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType) {
var match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr) {
var raw = unquoteString(rawStr);
var parseList = addressparser(raw);
var list = parseList.map(function (v) { return ({ name: v.name, email: v.address }); });
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str) {
var match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
var charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
var type = match[2].toUpperCase();
var value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(jsBase64.Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
}
else {
return decode(jsBase64.Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
}
else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str) {
var regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
var decodedString = str || '';
var spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(function (spin) {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value, charset, qEncoding) {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
if (qEncoding === void 0) { qEncoding = false; }
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
var rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
var error;
var result = {};
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
var lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options);
}
catch (e) {
error = e;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines, start, parent, options) {
var _a, _b, _c;
var boundary = null;
var lastHeaderName = '';
var findBoundary = '';
var insideBody = false;
var insideBoundary = false;
var isMultiHeader = false;
var isMultipart = false;
var checkedForCt = false;
var ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (var i = start; i < lines.length; i++) {
var line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
var ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
}
else {
checkedForCt = true;
var lineClone = Array.from(lines);
var string = lineClone.splice(i).join('\r\n');
var trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
}
else {
console.warn('Warning: undefined Content-Type');
}
}
}
else if (/^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
}
}
continue;
}
//Header value with new line
var match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
}
else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
}
else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
boundary = { boundary: match[1], lines: [] };
parent.body.push(boundary);
continue;
}
if (insideBoundary) {
//Search for boundary end
if (((_a = boundary) === null || _a === void 0 ? void 0 : _a.boundary) && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (((_b = boundary) === null || _b === void 0 ? void 0 : _b.boundary) && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
(_c = boundary) === null || _c === void 0 ? void 0 : _c.lines.push(line);
}
}
else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary) {
if (!boundary || !boundary.boundary) {
return null;
}
var lines = boundary.lines || [];
var result = {
boundary: boundary.boundary,
part: {
headers: {},
},
};
var lastHeaderName = '';
var insideBody = false;
var childBoundary;
for (var index = 0; index < lines.length; index++) {
var line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
var match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
var lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
}
else {
// part.body
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
var childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
}
else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
var child = completeBoundary(childBoundary);
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
}
else {
result.part.body = [child];
}
}
else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
}
else {
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(data, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var eml = '';
var EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
var readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
}
else if (readResult instanceof Error) {
throw readResult;
}
else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
var emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
var hasBoundary = false;
var boundary = createBoundary();
var multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
}
else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
var keys = Object.keys(data.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
}
else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
}
else {
//Array
for (var j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info("line 765 " + hasBoundary + ", emlBoundary: " + emlBoundary + ", multipartBoundary: " + multipartBoundary + ", boundary: " + boundary);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (var i = 0; i < data.attachments.length; i++) {
var attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
var content = jsBase64.Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
}
else {
//Buffer
// Uint8Array to string by new TextEncoder
var content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
}
catch (e) {
error = e;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var result;
//Appends the boundary to the result
function _append(headers, content, result) {
var contentType = headers['Content-Type'] || headers['Content-type'];
var contentDisposition = headers['Content-Disposition'];
var charset = getCharsetName(getCharset(contentType) || defaultCharset);
var encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8(content.replace(/\r?\n/g, '')));
}
else {
// string to Uint8Array by TextEncoder
content = encode(content.replace(/\r?\n/g, ''));
}
}
else if (encoding === 'quoted-printable') {
content = unquotePrintable(content, charset);
}
else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
var htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, "\"");
try {
if (encoding === 'base64') {
htmlContent = jsBase64.Base64.decode(htmlContent);
}
else if (jsBase64.Base64.btoa(jsBase64.Base64.atob(htmlContent)) == htmlContent) {
htmlContent = jsBase64.Base64.atob(htmlContent);
}
}
catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
}
else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
if (encoding === 'base64') {
content = jsBase64.Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
}
else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
var attachment = {};
var id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
var NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
var result_name = void 0;
for (var _i = 0, NameContainer_1 = NameContainer; _i < NameContainer_1.length; _i++) {
var key = NameContainer_1[_i];
var name = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(function (v) { return /name[\*]?="?(.+?)"?$/gi.exec(v); })
.reduce(function (a, b) {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
var ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
var cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content;
attachment.data64 = decode(content, charset);
result.attachments.push(attachment);
}
}
function _read(data) {
if (!data) {
return 'no data';
}
try {
var result_1 = {};
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result_1.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result_1.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result_1.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result_1.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result_1.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result_1.cc = getEmailAddress(data.headers['Cc']);
}
result_1.headers = data.headers;
//Content mime type
var boundary = null;
var ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (var i = 0; i < data.body.length; i++) {
var boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
}
else if (typeof boundaryBlock.part === 'string') {
result_1.data = boundaryBlock.part;
}
else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
}
else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result_1);
}
else {
// keep multipart/alternative
var currentHeaders = boundaryBlock.part.headers;
var currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log("line 969 currentHeadersContentType: " + currentHeadersContentType);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result_1.multipartAlternative) {
result_1.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (var j = 0; j < boundaryBlock.part.body.length; j++) {
var selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result_1.data = selfBoundary;
continue;
}
var headers = selfBoundary.part.headers;
var content = selfBoundary.part.body;
if (Array.isArray(content)) {
content.forEach(function (bound) {
_append(bound.part.headers, bound.part.body, result_1);
});
}
else {
_append(headers, content, result_1);
}
}
}
}
}
}
else if (typeof data.body === 'string') {
_append(data.headers, data.body, result_1);
}
return result_1;
}
catch (e) {
return e;
}
}
if (typeof eml === 'string') {
var parseResult = parse(eml, options);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
}
else {
var readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
}
else if (typeof eml === 'object') {
var readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
Object.defineProperty(exports, 'Base64', {
enumerable: true,
get: function () {
return jsBase64.Base64;
}
});
exports.GBKUTF8 = GB2312UTF8;
exports.buildEml = build;
exports.completeBoundary = completeBoundary;
exports.convert = convert;
exports.createBoundary = createBoundary;
exports.decode = decode;
exports.encode = encode;
exports.getBoundary = getBoundary;
exports.getCharset = getCharset;
exports.getEmailAddress = getEmailAddress;
exports.mimeDecode = mimeDecode;
exports.parseEml = parse;
exports.readEml = read;
exports.toEmailAddress = toEmailAddress;
exports.unquotePrintable = unquotePrintable;
exports.unquoteString = unquoteString;
Object.defineProperty(exports, '__esModule', { value: true });
});
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var jsBase64 = require('js-base64');
var textEncoding = require('@sinonjs/text-encoding');
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
var encode = function (str, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
return new textEncoding.TextEncoder(fromCharset).encode(str);
};
var arr2str = function (arr) {
var CHUNK_SZ = 0x8000;
var strs = [];
for (var i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ)));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
function decode(buf, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
var charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (var _i = 0, charsets_1 = charsets; _i < charsets_1.length; _i++) {
var _a = charsets_1[_i], charset = _a.charset, fatal = _a.fatal;
try {
return new textEncoding.TextDecoder(charset, { fatal: fatal }).decode(buf);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
var convert = function (data, fromCharset) {
return typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
};
function normalizeCharset(charset) {
if (charset === void 0) { charset = 'utf-8'; }
var match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* Gets the boundary name
* @param contentType - string
*/
function getBoundary(contentType) {
var match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
function getCharsetName(charset) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
function wrap(s, i) {
var a = [];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
function mimeDecode(str, fromCharset) {
if (str === void 0) { str = ''; }
if (fromCharset === void 0) { fromCharset = 'UTF-8'; }
var encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
var buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (var i = 0, len = str.length, bufferPos = 0; i < len; i++) {
var hex = str.substr(i + 1, 2);
var chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
}
else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* converting strings from gbk to utf-8
*/
var GB2312UTF8 = {
Dig2Dec: function (s) {
var retV = 0;
if (s.length == 4) {
for (var i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function (s) {
var retS = '';
var tempS = '';
var ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
var sss = '0123456789ABCDEF';
for (var i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function (n1) {
var s = '';
var n2 = 0;
for (var i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
}
else {
s += '0';
}
}
return s;
},
Str2Hex: function (s) {
var c = '';
var n;
var ss = '0123456789ABCDEF';
var digS = '';
for (var i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function (s1) {
var s = escape(s1);
var sa = s.split('%');
var retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (var i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function (str1) {
var substr = '';
var a = '';
var b = '';
var c = '';
var i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
}
else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
}
else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
var token;
var isGroup = false;
var state = 'text';
var address;
var addresses = [];
var data = {
address: [],
comment: [],
group: [],
text: [],
};
var i;
var len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
}
else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
}
else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
}
else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
}
else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
}
else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
var Tokenizer = /** @class */ (function () {
function Tokenizer(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
Tokenizer.prototype.checkChar = function (chr) {
if (this.escaped) ;
else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
}
else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
};
return Tokenizer;
}());
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
function addressparser(str, options) {
options = options || {};
var tokenizer = new Tokenizer(str);
var tokens = tokenizer.tokenize();
var addresses = [];
var address = [];
var parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
}
else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
var addresses_1 = [];
var walkAddressList_1 = function (list) {
list.forEach(function (address) {
if (address.group) {
return walkAddressList_1(address.group);
}
else {
addresses_1.push(address);
}
});
};
walkAddressList_1(parsedAddresses);
return addresses_1;
}
return parsedAddresses;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
/**
* log for test
*/
var verbose = false;
var defaultCharset = 'utf-8';
/**
* create a boundary
*/
function createBoundary() {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data) {
var email = '';
if (typeof data === 'undefined') ;
else if (typeof data === 'string') {
email = data;
}
else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(function (item) {
var str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(function (a) { return a; })
.join(', ');
}
else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType) {
var match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr) {
var raw = unquoteString(rawStr);
var parseList = addressparser(raw);
var list = parseList.map(function (v) { return ({ name: v.name, email: v.address }); });
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str) {
var match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
var charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
var type = match[2].toUpperCase();
var value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(jsBase64.Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
}
else {
return decode(jsBase64.Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
}
else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str) {
var regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
var decodedString = str || '';
var spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(function (spin) {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value, charset, qEncoding) {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
if (qEncoding === void 0) { qEncoding = false; }
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
var rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
var error;
var result = {};
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
var lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options);
}
catch (e) {
error = e;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines, start, parent, options) {
var _a, _b, _c;
var boundary = null;
var lastHeaderName = '';
var findBoundary = '';
var insideBody = false;
var insideBoundary = false;
var isMultiHeader = false;
var isMultipart = false;
var checkedForCt = false;
var ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (var i = start; i < lines.length; i++) {
var line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
var ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
}
else {
checkedForCt = true;
var lineClone = Array.from(lines);
var string = lineClone.splice(i).join('\r\n');
var trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
}
else {
console.warn('Warning: undefined Content-Type');
}
}
}
else if (/^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
}
}
continue;
}
//Header value with new line
var match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
}
else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
}
else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
boundary = { boundary: match[1], lines: [] };
parent.body.push(boundary);
continue;
}
if (insideBoundary) {
//Search for boundary end
if (((_a = boundary) === null || _a === void 0 ? void 0 : _a.boundary) && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (((_b = boundary) === null || _b === void 0 ? void 0 : _b.boundary) && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
(_c = boundary) === null || _c === void 0 ? void 0 : _c.lines.push(line);
}
}
else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary) {
if (!boundary || !boundary.boundary) {
return null;
}
var lines = boundary.lines || [];
var result = {
boundary: boundary.boundary,
part: {
headers: {},
},
};
var lastHeaderName = '';
var insideBody = false;
var childBoundary;
for (var index = 0; index < lines.length; index++) {
var line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
var match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
var lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
}
else {
// part.body
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
var childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
}
else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
var child = completeBoundary(childBoundary);
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
}
else {
result.part.body = [child];
}
}
else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
}
else {
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(data, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var eml = '';
var EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
var readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
}
else if (readResult instanceof Error) {
throw readResult;
}
else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
var emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
var hasBoundary = false;
var boundary = createBoundary();
var multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
}
else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
var keys = Object.keys(data.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
}
else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
}
else {
//Array
for (var j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info("line 765 " + hasBoundary + ", emlBoundary: " + emlBoundary + ", multipartBoundary: " + multipartBoundary + ", boundary: " + boundary);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (var i = 0; i < data.attachments.length; i++) {
var attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
var content = jsBase64.Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
}
else {
//Buffer
// Uint8Array to string by new TextEncoder
var content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
}
catch (e) {
error = e;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var result;
//Appends the boundary to the result
function _append(headers, content, result) {
var contentType = headers['Content-Type'] || headers['Content-type'];
var contentDisposition = headers['Content-Disposition'];
var charset = getCharsetName(getCharset(contentType) || defaultCharset);
var encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8(content.replace(/\r?\n/g, '')));
}
else {
// string to Uint8Array by TextEncoder
content = encode(content.replace(/\r?\n/g, ''));
}
}
else if (encoding === 'quoted-printable') {
content = unquotePrintable(content, charset);
}
else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
var htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, "\"");
try {
if (encoding === 'base64') {
htmlContent = jsBase64.Base64.decode(htmlContent);
}
else if (jsBase64.Base64.btoa(jsBase64.Base64.atob(htmlContent)) == htmlContent) {
htmlContent = jsBase64.Base64.atob(htmlContent);
}
}
catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
}
else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
if (encoding === 'base64') {
content = jsBase64.Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
}
else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
var attachment = {};
var id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
var NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
var result_name = void 0;
for (var _i = 0, NameContainer_1 = NameContainer; _i < NameContainer_1.length; _i++) {
var key = NameContainer_1[_i];
var name = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(function (v) { return /name[\*]?="?(.+?)"?$/gi.exec(v); })
.reduce(function (a, b) {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
var ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
var cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content;
attachment.data64 = decode(content, charset);
result.attachments.push(attachment);
}
}
function _read(data) {
if (!data) {
return 'no data';
}
try {
var result_1 = {};
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result_1.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result_1.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result_1.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result_1.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result_1.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result_1.cc = getEmailAddress(data.headers['Cc']);
}
result_1.headers = data.headers;
//Content mime type
var boundary = null;
var ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (var i = 0; i < data.body.length; i++) {
var boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
}
else if (typeof boundaryBlock.part === 'string') {
result_1.data = boundaryBlock.part;
}
else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
}
else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result_1);
}
else {
// keep multipart/alternative
var currentHeaders = boundaryBlock.part.headers;
var currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log("line 969 currentHeadersContentType: " + currentHeadersContentType);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result_1.multipartAlternative) {
result_1.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (var j = 0; j < boundaryBlock.part.body.length; j++) {
var selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result_1.data = selfBoundary;
continue;
}
var headers = selfBoundary.part.headers;
var content = selfBoundary.part.body;
if (Array.isArray(content)) {
content.forEach(function (bound) {
_append(bound.part.headers, bound.part.body, result_1);
});
}
else {
_append(headers, content, result_1);
}
}
}
}
}
}
else if (typeof data.body === 'string') {
_append(data.headers, data.body, result_1);
}
return result_1;
}
catch (e) {
return e;
}
}
if (typeof eml === 'string') {
var parseResult = parse(eml, options);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
}
else {
var readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
}
else if (typeof eml === 'object') {
var readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
Object.defineProperty(exports, 'Base64', {
enumerable: true,
get: function () {
return jsBase64.Base64;
}
});
exports.GBKUTF8 = GB2312UTF8;
exports.buildEml = build;
exports.completeBoundary = completeBoundary;
exports.convert = convert;
exports.createBoundary = createBoundary;
exports.decode = decode;
exports.encode = encode;
exports.getBoundary = getBoundary;
exports.getCharset = getCharset;
exports.getEmailAddress = getEmailAddress;
exports.mimeDecode = mimeDecode;
exports.parseEml = parse;
exports.readEml = read;
exports.toEmailAddress = toEmailAddress;
exports.unquotePrintable = unquotePrintable;
exports.unquoteString = unquoteString;
import { Base64 } from 'js-base64';
export { Base64 } from 'js-base64';
import { TextEncoder, TextDecoder } from '@sinonjs/text-encoding';
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
var encode = function (str, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
return new TextEncoder(fromCharset).encode(str);
};
var arr2str = function (arr) {
var CHUNK_SZ = 0x8000;
var strs = [];
for (var i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ)));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
function decode(buf, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
var charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (var _i = 0, charsets_1 = charsets; _i < charsets_1.length; _i++) {
var _a = charsets_1[_i], charset = _a.charset, fatal = _a.fatal;
try {
return new TextDecoder(charset, { fatal: fatal }).decode(buf);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
var convert = function (data, fromCharset) {
return typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
};
function normalizeCharset(charset) {
if (charset === void 0) { charset = 'utf-8'; }
var match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* Gets the boundary name
* @param contentType - string
*/
function getBoundary(contentType) {
var match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
function getCharsetName(charset) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
function wrap(s, i) {
var a = [];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
function mimeDecode(str, fromCharset) {
if (str === void 0) { str = ''; }
if (fromCharset === void 0) { fromCharset = 'UTF-8'; }
var encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
var buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (var i = 0, len = str.length, bufferPos = 0; i < len; i++) {
var hex = str.substr(i + 1, 2);
var chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
}
else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* converting strings from gbk to utf-8
*/
var GB2312UTF8 = {
Dig2Dec: function (s) {
var retV = 0;
if (s.length == 4) {
for (var i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function (s) {
var retS = '';
var tempS = '';
var ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
var sss = '0123456789ABCDEF';
for (var i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function (n1) {
var s = '';
var n2 = 0;
for (var i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
}
else {
s += '0';
}
}
return s;
},
Str2Hex: function (s) {
var c = '';
var n;
var ss = '0123456789ABCDEF';
var digS = '';
for (var i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function (s1) {
var s = escape(s1);
var sa = s.split('%');
var retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (var i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function (str1) {
var substr = '';
var a = '';
var b = '';
var c = '';
var i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
}
else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
}
else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
var token;
var isGroup = false;
var state = 'text';
var address;
var addresses = [];
var data = {
address: [],
comment: [],
group: [],
text: [],
};
var i;
var len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
}
else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
}
else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
}
else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
}
else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
}
else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
var Tokenizer = /** @class */ (function () {
function Tokenizer(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
Tokenizer.prototype.checkChar = function (chr) {
if (this.escaped) ;
else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
}
else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
};
return Tokenizer;
}());
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
function addressparser(str, options) {
options = options || {};
var tokenizer = new Tokenizer(str);
var tokens = tokenizer.tokenize();
var addresses = [];
var address = [];
var parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
}
else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
var addresses_1 = [];
var walkAddressList_1 = function (list) {
list.forEach(function (address) {
if (address.group) {
return walkAddressList_1(address.group);
}
else {
addresses_1.push(address);
}
});
};
walkAddressList_1(parsedAddresses);
return addresses_1;
}
return parsedAddresses;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
/**
* log for test
*/
var verbose = false;
var defaultCharset = 'utf-8';
/**
* create a boundary
*/
function createBoundary() {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data) {
var email = '';
if (typeof data === 'undefined') ;
else if (typeof data === 'string') {
email = data;
}
else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(function (item) {
var str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(function (a) { return a; })
.join(', ');
}
else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType) {
var match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr) {
var raw = unquoteString(rawStr);
var parseList = addressparser(raw);
var list = parseList.map(function (v) { return ({ name: v.name, email: v.address }); });
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str) {
var match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
var charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
var type = match[2].toUpperCase();
var value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
}
else {
return decode(Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
}
else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str) {
var regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
var decodedString = str || '';
var spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(function (spin) {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value, charset, qEncoding) {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
if (qEncoding === void 0) { qEncoding = false; }
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
var rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
var error;
var result = {};
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
var lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options);
}
catch (e) {
error = e;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines, start, parent, options) {
var _a, _b, _c;
var boundary = null;
var lastHeaderName = '';
var findBoundary = '';
var insideBody = false;
var insideBoundary = false;
var isMultiHeader = false;
var isMultipart = false;
var checkedForCt = false;
var ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (var i = start; i < lines.length; i++) {
var line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
var ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
}
else {
checkedForCt = true;
var lineClone = Array.from(lines);
var string = lineClone.splice(i).join('\r\n');
var trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
}
else {
console.warn('Warning: undefined Content-Type');
}
}
}
else if (/^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
}
}
continue;
}
//Header value with new line
var match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
}
else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
}
else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
boundary = { boundary: match[1], lines: [] };
parent.body.push(boundary);
continue;
}
if (insideBoundary) {
//Search for boundary end
if (((_a = boundary) === null || _a === void 0 ? void 0 : _a.boundary) && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (((_b = boundary) === null || _b === void 0 ? void 0 : _b.boundary) && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
(_c = boundary) === null || _c === void 0 ? void 0 : _c.lines.push(line);
}
}
else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary) {
if (!boundary || !boundary.boundary) {
return null;
}
var lines = boundary.lines || [];
var result = {
boundary: boundary.boundary,
part: {
headers: {},
},
};
var lastHeaderName = '';
var insideBody = false;
var childBoundary;
for (var index = 0; index < lines.length; index++) {
var line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
var match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
var lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
}
else {
// part.body
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
var childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
}
else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
var child = completeBoundary(childBoundary);
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
}
else {
result.part.body = [child];
}
}
else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
}
else {
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(data, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var eml = '';
var EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
var readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
}
else if (readResult instanceof Error) {
throw readResult;
}
else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
var emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
var hasBoundary = false;
var boundary = createBoundary();
var multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
}
else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
var keys = Object.keys(data.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
}
else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
}
else {
//Array
for (var j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info("line 765 " + hasBoundary + ", emlBoundary: " + emlBoundary + ", multipartBoundary: " + multipartBoundary + ", boundary: " + boundary);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (var i = 0; i < data.attachments.length; i++) {
var attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
var content = Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
}
else {
//Buffer
// Uint8Array to string by new TextEncoder
var content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
}
catch (e) {
error = e;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var result;
//Appends the boundary to the result
function _append(headers, content, result) {
var contentType = headers['Content-Type'] || headers['Content-type'];
var contentDisposition = headers['Content-Disposition'];
var charset = getCharsetName(getCharset(contentType) || defaultCharset);
var encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8(content.replace(/\r?\n/g, '')));
}
else {
// string to Uint8Array by TextEncoder
content = encode(content.replace(/\r?\n/g, ''));
}
}
else if (encoding === 'quoted-printable') {
content = unquotePrintable(content, charset);
}
else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
var htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, "\"");
try {
if (encoding === 'base64') {
htmlContent = Base64.decode(htmlContent);
}
else if (Base64.btoa(Base64.atob(htmlContent)) == htmlContent) {
htmlContent = Base64.atob(htmlContent);
}
}
catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
}
else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
if (encoding === 'base64') {
content = Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
}
else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
var attachment = {};
var id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
var NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
var result_name = void 0;
for (var _i = 0, NameContainer_1 = NameContainer; _i < NameContainer_1.length; _i++) {
var key = NameContainer_1[_i];
var name = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(function (v) { return /name[\*]?="?(.+?)"?$/gi.exec(v); })
.reduce(function (a, b) {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
var ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
var cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content;
attachment.data64 = decode(content, charset);
result.attachments.push(attachment);
}
}
function _read(data) {
if (!data) {
return 'no data';
}
try {
var result_1 = {};
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result_1.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result_1.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result_1.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result_1.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result_1.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result_1.cc = getEmailAddress(data.headers['Cc']);
}
result_1.headers = data.headers;
//Content mime type
var boundary = null;
var ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (var i = 0; i < data.body.length; i++) {
var boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
}
else if (typeof boundaryBlock.part === 'string') {
result_1.data = boundaryBlock.part;
}
else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
}
else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result_1);
}
else {
// keep multipart/alternative
var currentHeaders = boundaryBlock.part.headers;
var currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log("line 969 currentHeadersContentType: " + currentHeadersContentType);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result_1.multipartAlternative) {
result_1.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (var j = 0; j < boundaryBlock.part.body.length; j++) {
var selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result_1.data = selfBoundary;
continue;
}
var headers = selfBoundary.part.headers;
var content = selfBoundary.part.body;
if (Array.isArray(content)) {
content.forEach(function (bound) {
_append(bound.part.headers, bound.part.body, result_1);
});
}
else {
_append(headers, content, result_1);
}
}
}
}
}
}
else if (typeof data.body === 'string') {
_append(data.headers, data.body, result_1);
}
return result_1;
}
catch (e) {
return e;
}
}
if (typeof eml === 'string') {
var parseResult = parse(eml, options);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
}
else {
var readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
}
else if (typeof eml === 'object') {
var readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
export { GB2312UTF8 as GBKUTF8, build as buildEml, completeBoundary, convert, createBoundary, decode, encode, getBoundary, getCharset, getEmailAddress, mimeDecode, parse as parseEml, read as readEml, toEmailAddress, unquotePrintable, unquoteString };
(function (exports, jsBase64, textEncoding) {
'use strict';
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
var encode = function (str, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
return new textEncoding.TextEncoder(fromCharset).encode(str);
};
var arr2str = function (arr) {
var CHUNK_SZ = 0x8000;
var strs = [];
for (var i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ)));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
function decode(buf, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
var charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (var _i = 0, charsets_1 = charsets; _i < charsets_1.length; _i++) {
var _a = charsets_1[_i], charset = _a.charset, fatal = _a.fatal;
try {
return new textEncoding.TextDecoder(charset, { fatal: fatal }).decode(buf);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
var convert = function (data, fromCharset) {
return typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
};
function normalizeCharset(charset) {
if (charset === void 0) { charset = 'utf-8'; }
var match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* Gets the boundary name
* @param contentType - string
*/
function getBoundary(contentType) {
var match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
function getCharsetName(charset) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
function wrap(s, i) {
var a = [];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
function mimeDecode(str, fromCharset) {
if (str === void 0) { str = ''; }
if (fromCharset === void 0) { fromCharset = 'UTF-8'; }
var encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
var buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (var i = 0, len = str.length, bufferPos = 0; i < len; i++) {
var hex = str.substr(i + 1, 2);
var chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
}
else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* converting strings from gbk to utf-8
*/
var GB2312UTF8 = {
Dig2Dec: function (s) {
var retV = 0;
if (s.length == 4) {
for (var i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function (s) {
var retS = '';
var tempS = '';
var ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
var sss = '0123456789ABCDEF';
for (var i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function (n1) {
var s = '';
var n2 = 0;
for (var i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
}
else {
s += '0';
}
}
return s;
},
Str2Hex: function (s) {
var c = '';
var n;
var ss = '0123456789ABCDEF';
var digS = '';
for (var i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function (s1) {
var s = escape(s1);
var sa = s.split('%');
var retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (var i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function (str1) {
var substr = '';
var a = '';
var b = '';
var c = '';
var i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
}
else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
}
else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
var token;
var isGroup = false;
var state = 'text';
var address;
var addresses = [];
var data = {
address: [],
comment: [],
group: [],
text: [],
};
var i;
var len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
}
else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
}
else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
}
else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
}
else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
}
else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
var Tokenizer = /** @class */ (function () {
function Tokenizer(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
Tokenizer.prototype.checkChar = function (chr) {
if (this.escaped) ;
else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
}
else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
};
return Tokenizer;
}());
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
function addressparser(str, options) {
options = options || {};
var tokenizer = new Tokenizer(str);
var tokens = tokenizer.tokenize();
var addresses = [];
var address = [];
var parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
}
else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
var addresses_1 = [];
var walkAddressList_1 = function (list) {
list.forEach(function (address) {
if (address.group) {
return walkAddressList_1(address.group);
}
else {
addresses_1.push(address);
}
});
};
walkAddressList_1(parsedAddresses);
return addresses_1;
}
return parsedAddresses;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
/**
* log for test
*/
var verbose = false;
var defaultCharset = 'utf-8';
/**
* create a boundary
*/
function createBoundary() {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data) {
var email = '';
if (typeof data === 'undefined') ;
else if (typeof data === 'string') {
email = data;
}
else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(function (item) {
var str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(function (a) { return a; })
.join(', ');
}
else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType) {
var match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr) {
var raw = unquoteString(rawStr);
var parseList = addressparser(raw);
var list = parseList.map(function (v) { return ({ name: v.name, email: v.address }); });
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str) {
var match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
var charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
var type = match[2].toUpperCase();
var value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(jsBase64.Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
}
else {
return decode(jsBase64.Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
}
else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str) {
var regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
var decodedString = str || '';
var spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(function (spin) {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value, charset, qEncoding) {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
if (qEncoding === void 0) { qEncoding = false; }
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
var rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
var error;
var result = {};
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
var lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options);
}
catch (e) {
error = e;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines, start, parent, options) {
var _a, _b, _c;
var boundary = null;
var lastHeaderName = '';
var findBoundary = '';
var insideBody = false;
var insideBoundary = false;
var isMultiHeader = false;
var isMultipart = false;
var checkedForCt = false;
var ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (var i = start; i < lines.length; i++) {
var line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
var ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
}
else {
checkedForCt = true;
var lineClone = Array.from(lines);
var string = lineClone.splice(i).join('\r\n');
var trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
}
else {
console.warn('Warning: undefined Content-Type');
}
}
}
else if (/^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
}
}
continue;
}
//Header value with new line
var match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
}
else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
}
else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
boundary = { boundary: match[1], lines: [] };
parent.body.push(boundary);
continue;
}
if (insideBoundary) {
//Search for boundary end
if (((_a = boundary) === null || _a === void 0 ? void 0 : _a.boundary) && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (((_b = boundary) === null || _b === void 0 ? void 0 : _b.boundary) && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
(_c = boundary) === null || _c === void 0 ? void 0 : _c.lines.push(line);
}
}
else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary) {
if (!boundary || !boundary.boundary) {
return null;
}
var lines = boundary.lines || [];
var result = {
boundary: boundary.boundary,
part: {
headers: {},
},
};
var lastHeaderName = '';
var insideBody = false;
var childBoundary;
for (var index = 0; index < lines.length; index++) {
var line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
var match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
var lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
}
else {
// part.body
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
var childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
}
else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
var child = completeBoundary(childBoundary);
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
}
else {
result.part.body = [child];
}
}
else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
}
else {
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(data, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var eml = '';
var EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
var readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
}
else if (readResult instanceof Error) {
throw readResult;
}
else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
var emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
var hasBoundary = false;
var boundary = createBoundary();
var multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
}
else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
var keys = Object.keys(data.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
}
else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
}
else {
//Array
for (var j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info("line 765 " + hasBoundary + ", emlBoundary: " + emlBoundary + ", multipartBoundary: " + multipartBoundary + ", boundary: " + boundary);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (var i = 0; i < data.attachments.length; i++) {
var attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
var content = jsBase64.Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
}
else {
//Buffer
// Uint8Array to string by new TextEncoder
var content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
}
catch (e) {
error = e;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var result;
//Appends the boundary to the result
function _append(headers, content, result) {
var contentType = headers['Content-Type'] || headers['Content-type'];
var contentDisposition = headers['Content-Disposition'];
var charset = getCharsetName(getCharset(contentType) || defaultCharset);
var encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8(content.replace(/\r?\n/g, '')));
}
else {
// string to Uint8Array by TextEncoder
content = encode(content.replace(/\r?\n/g, ''));
}
}
else if (encoding === 'quoted-printable') {
content = unquotePrintable(content, charset);
}
else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
var htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, "\"");
try {
if (encoding === 'base64') {
htmlContent = jsBase64.Base64.decode(htmlContent);
}
else if (jsBase64.Base64.btoa(jsBase64.Base64.atob(htmlContent)) == htmlContent) {
htmlContent = jsBase64.Base64.atob(htmlContent);
}
}
catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
}
else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
if (encoding === 'base64') {
content = jsBase64.Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
}
else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
var attachment = {};
var id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
var NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
var result_name = void 0;
for (var _i = 0, NameContainer_1 = NameContainer; _i < NameContainer_1.length; _i++) {
var key = NameContainer_1[_i];
var name = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(function (v) { return /name[\*]?="?(.+?)"?$/gi.exec(v); })
.reduce(function (a, b) {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
var ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
var cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content;
attachment.data64 = decode(content, charset);
result.attachments.push(attachment);
}
}
function _read(data) {
if (!data) {
return 'no data';
}
try {
var result_1 = {};
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result_1.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result_1.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result_1.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result_1.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result_1.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result_1.cc = getEmailAddress(data.headers['Cc']);
}
result_1.headers = data.headers;
//Content mime type
var boundary = null;
var ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (var i = 0; i < data.body.length; i++) {
var boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
}
else if (typeof boundaryBlock.part === 'string') {
result_1.data = boundaryBlock.part;
}
else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
}
else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result_1);
}
else {
// keep multipart/alternative
var currentHeaders = boundaryBlock.part.headers;
var currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log("line 969 currentHeadersContentType: " + currentHeadersContentType);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result_1.multipartAlternative) {
result_1.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (var j = 0; j < boundaryBlock.part.body.length; j++) {
var selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result_1.data = selfBoundary;
continue;
}
var headers = selfBoundary.part.headers;
var content = selfBoundary.part.body;
if (Array.isArray(content)) {
content.forEach(function (bound) {
_append(bound.part.headers, bound.part.body, result_1);
});
}
else {
_append(headers, content, result_1);
}
}
}
}
}
}
else if (typeof data.body === 'string') {
_append(data.headers, data.body, result_1);
}
return result_1;
}
catch (e) {
return e;
}
}
if (typeof eml === 'string') {
var parseResult = parse(eml, options);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
}
else {
var readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
}
else if (typeof eml === 'object') {
var readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
Object.defineProperty(exports, 'Base64', {
enumerable: true,
get: function () {
return jsBase64.Base64;
}
});
exports.GBKUTF8 = GB2312UTF8;
exports.buildEml = build;
exports.completeBoundary = completeBoundary;
exports.convert = convert;
exports.createBoundary = createBoundary;
exports.decode = decode;
exports.encode = encode;
exports.getBoundary = getBoundary;
exports.getCharset = getCharset;
exports.getEmailAddress = getEmailAddress;
exports.mimeDecode = mimeDecode;
exports.parseEml = parse;
exports.readEml = read;
exports.toEmailAddress = toEmailAddress;
exports.unquotePrintable = unquotePrintable;
exports.unquoteString = unquoteString;
return exports;
}({}, Base64 || (window || this).Base64, {
TextEncoder: (window || this)['TextEncoder'],
TextDecoder: (window || this)['TextDecoder'],
}));
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('js-base64'), require('@sinonjs/text-encoding')) :
typeof define === 'function' && define.amd ? define(['exports', 'js-base64', '@sinonjs/text-encoding'], factory) :
(global = global || self, factory(global.EmlParseJs = {}, global.Base64, global.self));
}(this, (function (exports, jsBase64, textEncoding) { 'use strict';
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
var encode = function (str, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
return new textEncoding.TextEncoder(fromCharset).encode(str);
};
var arr2str = function (arr) {
var CHUNK_SZ = 0x8000;
var strs = [];
for (var i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ)));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
function decode(buf, fromCharset) {
if (fromCharset === void 0) { fromCharset = 'utf-8'; }
var charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (var _i = 0, charsets_1 = charsets; _i < charsets_1.length; _i++) {
var _a = charsets_1[_i], charset = _a.charset, fatal = _a.fatal;
try {
return new textEncoding.TextDecoder(charset, { fatal: fatal }).decode(buf);
// eslint-disable-next-line no-empty
}
catch (e) { }
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
var convert = function (data, fromCharset) {
return typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
};
function normalizeCharset(charset) {
if (charset === void 0) { charset = 'utf-8'; }
var match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* Gets the boundary name
* @param contentType - string
*/
function getBoundary(contentType) {
var match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
function getCharsetName(charset) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0, v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
function wrap(s, i) {
var a = [];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
function mimeDecode(str, fromCharset) {
if (str === void 0) { str = ''; }
if (fromCharset === void 0) { fromCharset = 'UTF-8'; }
var encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
var buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (var i = 0, len = str.length, bufferPos = 0; i < len; i++) {
var hex = str.substr(i + 1, 2);
var chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
}
else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* converting strings from gbk to utf-8
*/
var GB2312UTF8 = {
Dig2Dec: function (s) {
var retV = 0;
if (s.length == 4) {
for (var i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function (s) {
var retS = '';
var tempS = '';
var ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
var sss = '0123456789ABCDEF';
for (var i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function (n1) {
var s = '';
var n2 = 0;
for (var i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
}
else {
s += '0';
}
}
return s;
},
Str2Hex: function (s) {
var c = '';
var n;
var ss = '0123456789ABCDEF';
var digS = '';
for (var i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function (s1) {
var s = escape(s1);
var sa = s.split('%');
var retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (var i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function (str1) {
var substr = '';
var a = '';
var b = '';
var c = '';
var i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
}
else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
var widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
}
else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
var token;
var isGroup = false;
var state = 'text';
var address;
var addresses = [];
var data = {
address: [],
comment: [],
group: [],
text: [],
};
var i;
var len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
}
else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
}
else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
var _regexHandler = function (address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
}
else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
}
else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
}
else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
var Tokenizer = /** @class */ (function () {
function Tokenizer(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
Tokenizer.prototype.tokenize = function () {
var chr, list = [];
for (var i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(function (node) {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
};
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
Tokenizer.prototype.checkChar = function (chr) {
if (this.escaped) ;
else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
}
else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
}
else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
};
return Tokenizer;
}());
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
function addressparser(str, options) {
options = options || {};
var tokenizer = new Tokenizer(str);
var tokens = tokenizer.tokenize();
var addresses = [];
var address = [];
var parsedAddresses = [];
tokens.forEach(function (token) {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
}
else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(function (address) {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
var addresses_1 = [];
var walkAddressList_1 = function (list) {
list.forEach(function (address) {
if (address.group) {
return walkAddressList_1(address.group);
}
else {
addresses_1.push(address);
}
});
};
walkAddressList_1(parsedAddresses);
return addresses_1;
}
return parsedAddresses;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
/**
* log for test
*/
var verbose = false;
var defaultCharset = 'utf-8';
/**
* create a boundary
*/
function createBoundary() {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data) {
var email = '';
if (typeof data === 'undefined') ;
else if (typeof data === 'string') {
email = data;
}
else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(function (item) {
var str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(function (a) { return a; })
.join(', ');
}
else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType) {
var match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr) {
var raw = unquoteString(rawStr);
var parseList = addressparser(raw);
var list = parseList.map(function (v) { return ({ name: v.name, email: v.address }); });
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str) {
var match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
var charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
var type = match[2].toUpperCase();
var value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(jsBase64.Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
}
else {
return decode(jsBase64.Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
}
else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str) {
var regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
var decodedString = str || '';
var spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(function (spin) {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value, charset, qEncoding) {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
if (qEncoding === void 0) { qEncoding = false; }
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
var rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
var error;
var result = {};
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
var lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options);
}
catch (e) {
error = e;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines, start, parent, options) {
var _a, _b, _c;
var boundary = null;
var lastHeaderName = '';
var findBoundary = '';
var insideBody = false;
var insideBoundary = false;
var isMultiHeader = false;
var isMultipart = false;
var checkedForCt = false;
var ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (var i = start; i < lines.length; i++) {
var line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
var ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
}
else {
checkedForCt = true;
var lineClone = Array.from(lines);
var string = lineClone.splice(i).join('\r\n');
var trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
}
else {
console.warn('Warning: undefined Content-Type');
}
}
}
else if (/^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
}
}
continue;
}
//Header value with new line
var match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
}
else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
}
else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
boundary = { boundary: match[1], lines: [] };
parent.body.push(boundary);
continue;
}
if (insideBoundary) {
//Search for boundary end
if (((_a = boundary) === null || _a === void 0 ? void 0 : _a.boundary) && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (((_b = boundary) === null || _b === void 0 ? void 0 : _b.boundary) && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
(_c = boundary) === null || _c === void 0 ? void 0 : _c.lines.push(line);
}
}
else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary) {
if (!boundary || !boundary.boundary) {
return null;
}
var lines = boundary.lines || [];
var result = {
boundary: boundary.boundary,
part: {
headers: {},
},
};
var lastHeaderName = '';
var insideBody = false;
var childBoundary;
for (var index = 0; index < lines.length; index++) {
var line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
var match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
var lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
}
else {
// part.body
var match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
var childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
}
else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
var child = completeBoundary(childBoundary);
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
}
else {
result.part.body = [child];
}
}
else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
}
else {
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(data, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var eml = '';
var EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
var readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
}
else if (readResult instanceof Error) {
throw readResult;
}
else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
var emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
var hasBoundary = false;
var boundary = createBoundary();
var multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
}
else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
var keys = Object.keys(data.headers);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
}
else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
}
else {
//Array
for (var j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (var key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += key + ": " + data.textheaders[key].replace(/\r?\n/g, EOL + ' ');
}
}
}
else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info("line 765 " + hasBoundary + ", emlBoundary: " + emlBoundary + ", multipartBoundary: " + multipartBoundary + ", boundary: " + boundary);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (var i = 0; i < data.attachments.length; i++) {
var attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
var content = jsBase64.Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
}
else {
//Buffer
// Uint8Array to string by new TextEncoder
var content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
}
catch (e) {
error = e;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(eml, options, callback) {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
var error;
var result;
//Appends the boundary to the result
function _append(headers, content, result) {
var contentType = headers['Content-Type'] || headers['Content-type'];
var contentDisposition = headers['Content-Disposition'];
var charset = getCharsetName(getCharset(contentType) || defaultCharset);
var encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8(content.replace(/\r?\n/g, '')));
}
else {
// string to Uint8Array by TextEncoder
content = encode(content.replace(/\r?\n/g, ''));
}
}
else if (encoding === 'quoted-printable') {
content = unquotePrintable(content, charset);
}
else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
var htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, "\"");
try {
if (encoding === 'base64') {
htmlContent = jsBase64.Base64.decode(htmlContent);
}
else if (jsBase64.Base64.btoa(jsBase64.Base64.atob(htmlContent)) == htmlContent) {
htmlContent = jsBase64.Base64.atob(htmlContent);
}
}
catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
}
else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content, charset);
}
if (encoding === 'base64') {
content = jsBase64.Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
}
else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
}
else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
var attachment = {};
var id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
var NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
var result_name = void 0;
for (var _i = 0, NameContainer_1 = NameContainer; _i < NameContainer_1.length; _i++) {
var key = NameContainer_1[_i];
var name = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(function (v) { return /name[\*]?="?(.+?)"?$/gi.exec(v); })
.reduce(function (a, b) {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
var ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
var cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content;
attachment.data64 = decode(content, charset);
result.attachments.push(attachment);
}
}
function _read(data) {
if (!data) {
return 'no data';
}
try {
var result_1 = {};
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result_1.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result_1.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result_1.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result_1.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result_1.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result_1.cc = getEmailAddress(data.headers['Cc']);
}
result_1.headers = data.headers;
//Content mime type
var boundary = null;
var ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
var b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (var i = 0; i < data.body.length; i++) {
var boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
}
else if (typeof boundaryBlock.part === 'string') {
result_1.data = boundaryBlock.part;
}
else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
}
else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result_1);
}
else {
// keep multipart/alternative
var currentHeaders = boundaryBlock.part.headers;
var currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log("line 969 currentHeadersContentType: " + currentHeadersContentType);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result_1.multipartAlternative) {
result_1.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (var j = 0; j < boundaryBlock.part.body.length; j++) {
var selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result_1.data = selfBoundary;
continue;
}
var headers = selfBoundary.part.headers;
var content = selfBoundary.part.body;
if (Array.isArray(content)) {
content.forEach(function (bound) {
_append(bound.part.headers, bound.part.body, result_1);
});
}
else {
_append(headers, content, result_1);
}
}
}
}
}
}
else if (typeof data.body === 'string') {
_append(data.headers, data.body, result_1);
}
return result_1;
}
catch (e) {
return e;
}
}
if (typeof eml === 'string') {
var parseResult = parse(eml, options);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
}
else {
var readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
}
else if (typeof eml === 'object') {
var readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
}
else {
result = readResult;
}
}
else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
Object.defineProperty(exports, 'Base64', {
enumerable: true,
get: function () {
return jsBase64.Base64;
}
});
exports.GBKUTF8 = GB2312UTF8;
exports.buildEml = build;
exports.completeBoundary = completeBoundary;
exports.convert = convert;
exports.createBoundary = createBoundary;
exports.decode = decode;
exports.encode = encode;
exports.getBoundary = getBoundary;
exports.getCharset = getCharset;
exports.getEmailAddress = getEmailAddress;
exports.mimeDecode = mimeDecode;
exports.parseEml = parse;
exports.readEml = read;
exports.toEmailAddress = toEmailAddress;
exports.unquotePrintable = unquotePrintable;
exports.unquoteString = unquoteString;
Object.defineProperty(exports, '__esModule', { value: true });
})));
'use strict';
/**
* Converts tokens for a single address into an address object
*
* @param {Array} tokens Tokens object
* @return {Object} Address object
*/
function _handleAddress(tokens) {
let token;
let isGroup = false;
let state = 'text';
let address;
let addresses = [] as any[];
let data: any = {
address: [],
comment: [],
group: [],
text: [],
};
let i;
let len;
// Filter out <addresses>, (comments) and regular text
for (i = 0, len = tokens.length; i < len; i++) {
token = tokens[i];
if (token.type === 'operator') {
switch (token.value) {
case '<':
state = 'address';
break;
case '(':
state = 'comment';
break;
case ':':
state = 'group';
isGroup = true;
break;
default:
state = 'text';
}
} else if (token.value) {
if (state === 'address') {
// handle use case where unquoted name includes a "<"
// Apple Mail truncates everything between an unexpected < and an address
// and so will we
token.value = token.value.replace(/^[^<]*<\s*/, '');
}
data[state].push(token.value);
}
}
// If there is no text but a comment, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
if (isGroup) {
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
data.text = data.text.join(' ');
addresses.push({
name: data.text || (address && address.name),
group: data.group.length ? addressparser(data.group.join(',')) : [],
});
} else {
// If no address was found, try to detect one from regular text
if (!data.address.length && data.text.length) {
for (i = data.text.length - 1; i >= 0; i--) {
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
data.address = data.text.splice(i, 1);
break;
}
}
let _regexHandler = function(address) {
if (!data.address.length) {
data.address = [address.trim()];
return ' ';
} else {
return address;
}
};
// still no address
if (!data.address.length) {
for (i = data.text.length - 1; i >= 0; i--) {
// fixed the regex to parse email address correctly when email address has more than one @
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
if (data.address.length) {
break;
}
}
}
}
// If there's still is no text but a comment exixts, replace the two
if (!data.text.length && data.comment.length) {
data.text = data.comment;
data.comment = [];
}
// Keep only the first address occurence, push others to regular text
if (data.address.length > 1) {
data.text = data.text.concat(data.address.splice(1));
}
// Join values with spaces
data.text = data.text.join(' ');
data.address = data.address.join(' ');
if (!data.address && isGroup) {
return [];
} else {
address = {
address: data.address || data.text || '',
name: data.text || data.address || '',
};
if (address.address === address.name) {
if ((address.address || '').match(/@/)) {
address.name = '';
} else {
address.address = '';
}
}
addresses.push(address);
}
}
return addresses;
}
/**
* Creates a Tokenizer object for tokenizing address field strings
*
* @constructor
* @param {String} str Address field string
*/
export class Tokenizer {
str: string;
operatorCurrent: string;
operatorExpecting: string;
node: any;
escaped: boolean;
list: any[];
operators: any;
constructor(str) {
this.str = (str || '').toString();
this.operatorCurrent = '';
this.operatorExpecting = '';
this.node = null;
this.escaped = false;
this.list = [];
/**
* Operator tokens and which tokens are expected to end the sequence
*/
this.operators = {
'"': '"',
'(': ')',
'<': '>',
',': '',
':': ';',
// Semicolons are not a legal delimiter per the RFC2822 grammar other
// than for terminating a group, but they are also not valid for any
// other use in this context. Given that some mail clients have
// historically allowed the semicolon as a delimiter equivalent to the
// comma in their UI, it makes sense to treat them the same as a comma
// when used outside of a group.
';': '',
};
}
/**
* Tokenizes the original input string
*
* @return {Array} An array of operator|text tokens
*/
tokenize() {
let chr,
list: any[] = [];
for (let i = 0, len = this.str.length; i < len; i++) {
chr = this.str.charAt(i);
this.checkChar(chr);
}
this.list.forEach(node => {
node.value = (node.value || '').toString().trim();
if (node.value) {
list.push(node);
}
});
return list;
}
/**
* Checks if a character is an operator or text and acts accordingly
*
* @param {String} chr Character from the address field
*/
checkChar(chr) {
if (this.escaped) {
// ignore next condition blocks
} else if (chr === this.operatorExpecting) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = '';
this.escaped = false;
return;
} else if (!this.operatorExpecting && chr in this.operators) {
this.node = {
type: 'operator',
value: chr,
};
this.list.push(this.node);
this.node = null;
this.operatorExpecting = this.operators[chr];
this.escaped = false;
return;
} else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
this.escaped = true;
return;
}
if (!this.node) {
this.node = {
type: 'text',
value: '',
};
this.list.push(this.node);
}
if (chr === '\n') {
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
chr = ' ';
}
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
// skip command bytes
this.node.value += chr;
}
this.escaped = false;
}
}
/**
* Parses structured e-mail addresses from an address field
*
* Example:
*
* 'Name <address@domain>'
*
* will be converted to
*
* [{name: 'Name', address: 'address@domain'}]
*
* @param {String} str Address field
* @return {Array} An array of address objects
*/
export function addressparser(str, options?: any): { name?: string; address?: string }[] {
options = options || {};
let tokenizer = new Tokenizer(str);
let tokens = tokenizer.tokenize();
let addresses = [] as any[];
let address = [] as any[];
let parsedAddresses = [] as any[];
tokens.forEach(token => {
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
if (address.length) {
addresses.push(address);
}
address = [];
} else {
address.push(token);
}
});
if (address.length) {
addresses.push(address);
}
addresses.forEach(address => {
address = _handleAddress(address);
if (address.length) {
parsedAddresses = parsedAddresses.concat(address);
}
});
if (options.flatten) {
let addresses = [] as any[];
let walkAddressList = list => {
list.forEach(address => {
if (address.group) {
return walkAddressList(address.group);
} else {
addresses.push(address);
}
});
};
walkAddressList(parsedAddresses);
return addresses;
}
return parsedAddresses;
}
import { TextDecoder, TextEncoder } from '@sinonjs/text-encoding';
/**
* Encodes an unicode string into an Uint8Array object as UTF-8
*
* @param {String} str String to be encoded
* @return {Uint8Array} UTF-8 encoded typed array
*/
export const encode = (str: string, fromCharset = 'utf-8'): Uint8Array => new TextEncoder(fromCharset).encode(str);
export const arr2str = (arr: Uint8Array) => {
const CHUNK_SZ = 0x8000;
const strs = [] as any[];
for (let i = 0; i < arr.length; i += CHUNK_SZ) {
strs.push(String.fromCharCode.apply(null, arr.subarray(i, i + CHUNK_SZ) as any));
}
return strs.join('');
};
/**
* Decodes a string from Uint8Array to an unicode string using specified encoding
*
* @param {Uint8Array} buf Binary data to be decoded
* @param {String} Binary data is decoded into string using this charset
* @return {String} Decoded string
*/
export function decode(buf: Uint8Array, fromCharset = 'utf-8'): string {
const charsets = [
{ charset: normalizeCharset(fromCharset), fatal: false },
{ charset: 'utf-8', fatal: true },
{ charset: 'iso-8859-15', fatal: false },
];
for (const { charset, fatal } of charsets) {
try {
return new TextDecoder(charset, { fatal }).decode(buf);
// eslint-disable-next-line no-empty
} catch (e) {}
}
return arr2str(buf); // all else fails, treat it as binary
}
/**
* Convert a string from specific encoding to UTF-8 Uint8Array
*
* @param {String|Uint8Array} data Data to be encoded
* @param {String} Source encoding for the string (optional for data of type String)
* @return {Uint8Array} UTF-8 encoded typed array
*/
export const convert = (data: string | Uint8Array, fromCharset?: string | undefined): Uint8Array =>
typeof data === 'string' ? encode(data) : encode(decode(data, fromCharset));
function normalizeCharset(charset = 'utf-8') {
let match;
if ((match = charset.match(/^utf[-_]?(\d+)$/i))) {
return 'UTF-' + match[1];
}
if ((match = charset.match(/^win[-_]?(\d+)$/i))) {
return 'WINDOWS-' + match[1];
}
if ((match = charset.match(/^latin[-_]?(\d+)$/i))) {
return 'ISO-8859-' + match[1];
}
return charset;
}
/**
* @author superchow
* @emil superchow@live.cn
*/
import { Base64 } from 'js-base64';
import { convert, decode, encode } from './charset';
import { GB2312UTF8, getCharsetName, guid, mimeDecode, wrap, getBoundary } from './utils';
import {
KeyValue,
EmailAddress,
ParsedEmlJson,
ReadedEmlJson,
Attachment,
EmlHeaders,
Options,
BuildOptions,
CallbackFn,
OptionOrNull,
BoundaryRawData,
BoundaryConvertedData,
} from './interface';
import { addressparser } from './addressparser';
/**
* log for test
*/
let verbose: boolean = false;
const defaultCharset = 'utf-8';
const fileExtensions: KeyValue = {
'text/plain': '.txt',
'text/html': '.html',
'image/png': '.png',
'image/jpg': '.jpg',
'image/jpeg': '.jpg',
};
/**
* Gets file extension by mime type
* @param {String} mimeType
* @returns {String}
*/
// eslint-disable-next-line no-unused-vars
function getFileExtension(mimeType: string): string {
return fileExtensions[mimeType] || '';
}
/**
* create a boundary
*/
function createBoundary(): string {
return '----=' + guid();
}
/**
* Builds e-mail address string, e.g. { name: 'PayPal', email: 'noreply@paypal.com' } => 'PayPal' <noreply@paypal.com>
* @param {String|EmailAddress|EmailAddress[]|null} data
*/
function toEmailAddress(data?: string | EmailAddress | EmailAddress[] | null): string {
let email = '';
if (typeof data === 'undefined') {
//No e-mail address
} else if (typeof data === 'string') {
email = data;
} else if (typeof data === 'object') {
if (Array.isArray(data)) {
email += data
.map(item => {
let str = '';
if (item.name) {
str += '"' + item.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (item.email) {
str += '<' + item.email + '>';
}
return str;
})
.filter(a => a)
.join(', ');
} else {
if (data) {
if (data.name) {
email += '"' + data.name.replace(/^"|"\s*$/g, '') + '" ';
}
if (data.email) {
email += '<' + data.email + '>';
}
}
}
}
return email;
}
/**
* Gets character set name, e.g. contentType='.....charset='iso-8859-2'....'
* @param {String} contentType
* @returns {String|undefined}
*/
function getCharset(contentType: string) {
const match = /charset\s*=\W*([\w\-]+)/g.exec(contentType);
return match ? match[1] : undefined;
}
/**
* Gets name and e-mail address from a string, e.g. 'PayPal' <noreply@paypal.com> => { name: 'PayPal', email: 'noreply@paypal.com' }
* @param {String} raw
* @returns { EmailAddress | EmailAddress[] | null}
*/
function getEmailAddress(rawStr: string): EmailAddress | EmailAddress[] | null {
const raw = unquoteString(rawStr);
const parseList = addressparser(raw);
const list = parseList.map(v => ({ name: v.name, email: v.address } as EmailAddress));
//Return result
if (list.length === 0) {
return null; //No e-mail address
}
if (list.length === 1) {
return list[0]; //Only one record, return as object, required to preserve backward compatibility
}
return list; //Multiple e-mail addresses as array
}
/**
* decode one joint
* @param {String} str
* @returns {String}
*/
function decodeJoint(str: string) {
const match = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi.exec(str);
if (match) {
const charset = getCharsetName(match[1] || defaultCharset); //eq. match[1] = 'iso-8859-2'; charset = 'iso88592'
const type = match[2].toUpperCase();
const value = match[3];
if (type === 'B') {
//Base64
if (charset === 'utf8') {
return decode(encode(Base64.fromBase64(value.replace(/\r?\n/g, ''))), 'utf8');
} else {
return decode(Base64.toUint8Array(value.replace(/\r?\n/g, '')), charset);
}
} else if (type === 'Q') {
//Quoted printable
return unquotePrintable(value, charset, true);
}
}
return str;
}
/**
* decode section
* @param {String} str
* @returns {String}
*/
function unquoteString(str: string): string {
const regex = /=\?([^?]+)\?(B|Q)\?(.+?)(\?=)/gi;
let decodedString = str || '';
const spinOffMatch = decodedString.match(regex);
if (spinOffMatch) {
spinOffMatch.forEach(spin => {
decodedString = decodedString.replace(spin, decodeJoint(spin));
});
}
return decodedString.replace(/\r?\n/g, '');
}
/**
* Decodes 'quoted-printable'
* @param {String} value
* @param {String} charset
* @param {String} qEncoding whether the encoding is RFC-2047’s Q-encoding, meaning special handling of underscores.
* @returns {String}
*/
function unquotePrintable(value: string, charset?: string, qEncoding = false): string {
//Convert =0D to '\r', =20 to ' ', etc.
// if (!charset || charset == "utf8" || charset == "utf-8") {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, p3, offset, string) {
// })
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) { return String.fromCharCode(parseInt(p1, 16)); })
// .replace(/=\r?\n/gi, ""); //Join line
// } else {
// return value
// .replace(/=([\w\d]{2})=([\w\d]{2})/gi, function (matcher, p1, p2, offset, string) {
// })
// .replace(/=([\w\d]{2})/gi, function (matcher, p1, offset, string) {
// })
// .replace(/=\r?\n/gi, ''); //Join line
// }
let rawString = value
.replace(/[\t ]+$/gm, '') // remove invalid whitespace from the end of lines
.replace(/=(?:\r?\n|$)/g, ''); // remove soft line breaks
if (qEncoding) {
rawString = rawString.replace(/_/g, decode(new Uint8Array([0x20]), charset));
}
return mimeDecode(rawString, charset);
}
/**
* Parses EML file content and returns object-oriented representation of the content.
* @param {String} eml
* @param {OptionOrNull | CallbackFn<ParsedEmlJson>} options
* @param {CallbackFn<ParsedEmlJson>} callback
* @returns {string | Error | ParsedEmlJson}
*/
function parse(
eml: string,
options?: OptionOrNull | CallbackFn<ParsedEmlJson>,
callback?: CallbackFn<ParsedEmlJson>
): string | Error | ParsedEmlJson {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
if (typeof options !== 'object') {
options = { headersOnly: false };
}
let error: string | Error | undefined;
let result: ParsedEmlJson | undefined = {} as ParsedEmlJson;
try {
if (typeof eml !== 'string') {
throw new Error('Argument "eml" expected to be string!');
}
const lines = eml.split(/\r?\n/);
result = parseRecursive(lines, 0, result, options as Options) as ParsedEmlJson;
} catch (e) {
error = e as string;
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* Parses EML file content.
* @param {String[]} lines
* @param {Number} start
* @param {Options} options
* @returns {ParsedEmlJson}
*/
function parseRecursive(lines: string[], start: number, parent: any, options: Options) {
let boundary: any = null;
let lastHeaderName = '';
let findBoundary = '';
let insideBody = false;
let insideBoundary = false;
let isMultiHeader = false;
let isMultipart = false;
let checkedForCt = false;
let ctInBody = false;
parent.headers = {};
//parent.body = null;
function complete(boundary: any) {
//boundary.part = boundary.lines.join("\r\n");
boundary.part = {};
parseRecursive(boundary.lines, 0, boundary.part, options);
delete boundary.lines;
}
//Read line by line
for (let i = start; i < lines.length; i++) {
let line = lines[i];
//Header
if (!insideBody) {
//Search for empty line
if (line == '') {
insideBody = true;
if (options && options.headersOnly) {
break;
}
//Expected boundary
let ct = parent.headers['Content-Type'] || parent.headers['Content-type'];
if (!ct) {
if (checkedForCt) {
insideBody = !ctInBody;
} else {
checkedForCt = true;
const lineClone = Array.from(lines);
const string = lineClone.splice(i).join('\r\n');
const trimmedStrin = string.trim();
if (trimmedStrin.indexOf('Content-Type') === 0 || trimmedStrin.indexOf('Content-type') === 0) {
insideBody = false;
ctInBody = true;
} else {
console.warn('Warning: undefined Content-Type');
}
}
} else if (/^multipart\//g.test(ct)) {
let b = getBoundary(ct);
if (b && b.length) {
findBoundary = b;
isMultipart = true;
parent.body = [];
} else {
if (verbose) {
console.warn('Multipart without boundary! ' + ct.replace(/\r?\n/g, ' '));
}
}
}
continue;
}
//Header value with new line
let match = /^\s+([^\r\n]+)/g.exec(line);
if (match) {
if (isMultiHeader) {
parent.headers[lastHeaderName][parent.headers[lastHeaderName].length - 1] += '\r\n' + match[1];
} else {
parent.headers[lastHeaderName] += '\r\n' + match[1];
}
continue;
}
//Header name and value
match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
if (parent.headers[lastHeaderName]) {
//Multiple headers with the same name
isMultiHeader = true;
if (typeof parent.headers[lastHeaderName] == 'string') {
parent.headers[lastHeaderName] = [parent.headers[lastHeaderName]];
}
parent.headers[lastHeaderName].push(match[2]);
} else {
//Header first appeared here
isMultiHeader = false;
parent.headers[lastHeaderName] = match[2];
}
continue;
}
}
//Body
else {
//Multipart body
if (isMultipart) {
//Search for boundary start
//Updated on 2019-10-12: A line before the boundary marker is not required to be an empty line
//if (lines[i - 1] == "" && line.indexOf("--" + findBoundary) == 0 && !/\-\-(\r?\n)?$/g.test(line)) {
if (line.indexOf('--' + findBoundary) == 0 && line.indexOf('--' + findBoundary + '--') !== 0) {
insideBoundary = true;
//Complete the previous boundary
if (boundary && boundary.lines) {
complete(boundary);
}
//Start a new boundary
let match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line) as RegExpExecArray;
boundary = { boundary: match[1], lines: [] as any[] };
parent.body.push(boundary);
if (verbose) {
console.log('Found boundary: ' + boundary.boundary);
}
continue;
}
if (insideBoundary) {
//Search for boundary end
if (boundary?.boundary && lines[i - 1] == '' && line.indexOf('--' + findBoundary + '--') == 0) {
insideBoundary = false;
complete(boundary);
continue;
}
if (boundary?.boundary && line.indexOf('--' + findBoundary + '--') == 0) {
continue;
}
boundary?.lines.push(line);
}
} else {
//Solid string body
parent.body = lines.splice(i).join('\r\n');
break;
}
}
}
//Complete the last boundary
if (parent.body && parent.body.length && parent.body[parent.body.length - 1].lines) {
complete(parent.body[parent.body.length - 1]);
}
return parent;
}
/**
* Convert BoundaryRawData to BoundaryConvertedData
* @param {BoundaryRawData} boundary
* @returns {BoundaryConvertedData} Obj
*/
function completeBoundary(boundary: BoundaryRawData): BoundaryConvertedData | null {
if (!boundary || !boundary.boundary) {
return null;
}
const lines = boundary.lines || [];
const result = {
boundary: boundary.boundary,
part: {
headers: {},
},
} as BoundaryConvertedData;
let lastHeaderName = '';
let insideBody = false;
let childBoundary: BoundaryRawData | undefined;
for (let index = 0; index < lines.length; index++) {
const line = lines[index];
if (!insideBody) {
if (line === '') {
insideBody = true;
continue;
}
const match = /^([\w\d\-]+):\s*([^\r\n]*)/gi.exec(line);
if (match) {
lastHeaderName = match[1];
result.part.headers[lastHeaderName] = match[2];
continue;
}
//Header value with new line
const lineMatch = /^\s+([^\r\n]+)/g.exec(line);
if (lineMatch) {
result.part.headers[lastHeaderName] += '\r\n' + lineMatch[1];
continue;
}
} else {
// part.body
const match = /^\-\-([^\r\n]+)(\r?\n)?$/g.exec(line);
const childBoundaryStr = getBoundary(result.part.headers['Content-Type'] || result.part.headers['Content-type']);
if (verbose) {
if (match) {
console.log(`line 568: line is ${line}, ${'--' + childBoundaryStr}`, `${line.indexOf('--' + childBoundaryStr)}`);
}
}
if (match && line.indexOf('--' + childBoundaryStr) === 0 && !childBoundary) {
childBoundary = { boundary: match ? match[1] : '', lines: [] };
continue;
} else if (!!childBoundary && childBoundary.boundary) {
if (lines[index - 1] === '' && line.indexOf('--' + childBoundary.boundary) === 0) {
const child = completeBoundary(childBoundary);
if (verbose) {
console.info(`578: ${JSON.stringify(child)}`);
}
if (child) {
if (Array.isArray(result.part.body)) {
result.part.body.push(child);
} else {
result.part.body = [child];
}
} else {
result.part.body = childBoundary.lines.join('\r\n');
}
// next line child
if (!!lines[index + 1]) {
childBoundary.lines = [];
continue;
}
// end line child And this boundary's end
if (line.indexOf('--' + childBoundary.boundary + '--') === 0 && lines[index + 1] === '') {
if (verbose) {
console.info('line 601 childBoundary is over line is 534');
}
childBoundary = undefined;
break;
}
}
childBoundary.lines.push(line);
} else {
if (verbose) {
console.warn('body is string');
}
result.part.body = lines.splice(index).join('\r\n');
break;
}
}
}
return result;
}
/**
* buid EML file by ReadedEmlJson or EML file content
* @param {ReadedEmlJson} data
* @param {BuildOptions | CallbackFn<string> | null} options
* @param {CallbackFn<string>} callback
*/
function build(
data: ReadedEmlJson | string,
options?: BuildOptions | CallbackFn<string> | null,
callback?: CallbackFn<string>
): string | Error {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
let error: Error | string | undefined;
let eml = '';
const EOL = '\r\n'; //End-of-line
try {
if (!data) {
throw new Error('Argument "data" expected to be an object! or string');
}
if (typeof data === 'string') {
const readResult = read(data);
if (typeof readResult === 'string') {
throw new Error(readResult);
} else if (readResult instanceof Error) {
throw readResult;
} else {
data = readResult;
}
}
if (!data.headers) {
throw new Error('Argument "data" expected to be has headers');
}
if (typeof data.subject === 'string') {
data.headers['Subject'] = data.subject;
}
if (typeof data.from !== 'undefined') {
data.headers['From'] = toEmailAddress(data.from);
}
if (typeof data.to !== 'undefined') {
data.headers['To'] = toEmailAddress(data.to);
}
if (typeof data.cc !== 'undefined') {
data.headers['Cc'] = toEmailAddress(data.cc);
}
// if (!data.headers['To']) {
// throw new Error('Missing "To" e-mail address!');
// }
const emlBoundary = getBoundary(data.headers['Content-Type'] || data.headers['Content-type'] || '');
let hasBoundary = false;
let boundary = createBoundary();
let multipartBoundary = '';
if (data.multipartAlternative) {
multipartBoundary = '' + (getBoundary(data.multipartAlternative['Content-Type']) || '');
hasBoundary = true;
}
if (emlBoundary) {
boundary = emlBoundary;
hasBoundary = true;
} else {
data.headers['Content-Type'] = data.headers['Content-type'] || 'multipart/mixed;' + EOL + 'boundary="' + boundary + '"';
// Restrained
// hasBoundary = true;
}
//Build headers
const keys = Object.keys(data.headers);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value: string | string[] = data.headers[key];
if (typeof value === 'undefined') {
continue; //Skip missing headers
} else if (typeof value === 'string') {
eml += key + ': ' + value.replace(/\r?\n/g, EOL + ' ') + EOL;
} else {
//Array
for (let j = 0; j < value.length; j++) {
eml += key + ': ' + value[j].replace(/\r?\n/g, EOL + ' ') + EOL;
}
}
}
if (data.multipartAlternative) {
eml += EOL;
eml += '--' + emlBoundary + EOL;
eml += 'Content-Type: ' + data.multipartAlternative['Content-Type'].replace(/\r?\n/g, EOL + ' ') + EOL;
}
//Start the body
eml += EOL;
//Plain text content
if (data.text) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (const key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += `${key}: ${data.textheaders[key].replace(/\r?\n/g, EOL + ' ')}`;
}
}
} else if (hasBoundary) {
// else Assembly
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/plain; charset="utf-8"' + EOL;
}
eml += EOL + data.text;
eml += EOL;
}
//HTML content
if (data.html) {
// Encode opened and self headers keeped
if (typeof options === 'object' && !!options && options.encode && data.textheaders) {
eml += '--' + boundary + EOL;
for (const key in data.textheaders) {
if (data.textheaders.hasOwnProperty(key)) {
eml += `${key}: ${data.textheaders[key].replace(/\r?\n/g, EOL + ' ')}`;
}
}
} else if (hasBoundary) {
eml += '--' + (multipartBoundary ? multipartBoundary : boundary) + EOL;
eml += 'Content-Type: text/html; charset="utf-8"' + EOL;
}
if (verbose) {
console.info(
`line 765 ${hasBoundary}, emlBoundary: ${emlBoundary}, multipartBoundary: ${multipartBoundary}, boundary: ${boundary}`
);
}
eml += EOL + data.html;
eml += EOL;
}
//Append attachments
if (data.attachments) {
for (let i = 0; i < data.attachments.length; i++) {
const attachment = data.attachments[i];
eml += '--' + boundary + EOL;
eml += 'Content-Type: ' + (attachment.contentType.replace(/\r?\n/g, EOL + ' ') || 'application/octet-stream') + EOL;
eml += 'Content-Transfer-Encoding: base64' + EOL;
eml +=
'Content-Disposition: ' +
(attachment.inline ? 'inline' : 'attachment') +
'; filename="' +
(attachment.filename || attachment.name || 'attachment_' + (i + 1)) +
'"' +
EOL;
if (attachment.cid) {
eml += 'Content-ID: <' + attachment.cid + '>' + EOL;
}
eml += EOL;
if (typeof attachment.data === 'string') {
const content = Base64.toBase64(attachment.data);
eml += wrap(content, 72) + EOL;
} else {
//Buffer
// Uint8Array to string by new TextEncoder
const content = decode(attachment.data);
eml += wrap(content, 72) + EOL;
}
eml += EOL;
}
}
//Finish the boundary
if (hasBoundary) {
eml += '--' + boundary + '--' + EOL;
}
} catch (e) {
error = e as string;
}
callback && callback(error, eml);
return error || eml;
}
/**
* Parses EML file content and return user-friendly object.
* @param {String | ParsedEmlJson} eml EML file content or object from 'parse'
* @param { OptionOrNull | CallbackFn<ReadedEmlJson>} options EML parse options
* @param {CallbackFn<ReadedEmlJson>} callback Callback function(error, data)
*/
function read(
eml: string | ParsedEmlJson,
options?: OptionOrNull | CallbackFn<ReadedEmlJson>,
callback?: CallbackFn<ReadedEmlJson>
): ReadedEmlJson | Error | string {
//Shift arguments
if (typeof options === 'function' && typeof callback === 'undefined') {
callback = options;
options = null;
}
let error: Error | string | undefined;
let result: ReadedEmlJson | undefined;
//Appends the boundary to the result
function _append(headers: EmlHeaders, content: string | Uint8Array | Attachment, result: ReadedEmlJson) {
const contentType = headers['Content-Type'] || headers['Content-type'];
const contentDisposition = headers['Content-Disposition'];
const charset = getCharsetName(getCharset(contentType as string) || defaultCharset);
let encoding = headers['Content-Transfer-Encoding'] || headers['Content-transfer-encoding'];
if (typeof encoding === 'string') {
encoding = encoding.toLowerCase();
}
if (encoding === 'base64') {
if (contentType && contentType.indexOf('gbk') >= 0) {
// is work? I'm not sure
content = encode(GB2312UTF8.GB2312ToUTF8((content as string).replace(/\r?\n/g, '')));
} else {
// string to Uint8Array by TextEncoder
content = encode((content as string).replace(/\r?\n/g, ''));
}
} else if (encoding === 'quoted-printable') {
content = unquotePrintable(content as string, charset);
} else if (encoding && charset !== 'utf8' && encoding.search(/binary|8bit/) === 0) {
//'8bit', 'binary', '8bitmime', 'binarymime'
content = decode(content as Uint8Array, charset);
}
if (!contentDisposition && contentType && contentType.indexOf('text/html') >= 0) {
if (typeof content !== 'string') {
content = decode(content as Uint8Array, charset);
}
let htmlContent = content.replace(/\r\n|(&quot;)/g, '').replace(/\"/g, `"`);
try {
if (encoding === 'base64') {
htmlContent = Base64.decode(htmlContent);
} else if (Base64.btoa(Base64.atob(htmlContent)) == htmlContent) {
htmlContent = Base64.atob(htmlContent);
}
} catch (error) {
console.error(error);
}
if (result.html) {
result.html += htmlContent;
} else {
result.html = htmlContent;
}
result.htmlheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
} else if (!contentDisposition && contentType && contentType.indexOf('text/plain') >= 0) {
if (typeof content !== 'string') {
content = decode(content as Uint8Array, charset);
}
if (encoding === 'base64') {
content = Base64.decode(content);
}
//Plain text message
if (result.text) {
result.text += content;
} else {
result.text = content;
}
result.textheaders = {
'Content-Type': contentType,
'Content-Transfer-Encoding': encoding || '',
};
// self boundary Not used at conversion
} else {
//Get the attachment
if (!result.attachments) {
result.attachments = [];
}
const attachment = {} as Attachment;
const id = headers['Content-ID'] || headers['Content-Id'];
if (id) {
attachment.id = id;
}
const NameContainer = ['Content-Disposition', 'Content-Type', 'Content-type'];
let result_name;
for (const key of NameContainer) {
const name: string = headers[key];
if (name) {
result_name = name
.replace(/(\s|'|utf-8|\*[0-9]\*)/g, '')
.split(';')
.map(v => /name[\*]?="?(.+?)"?$/gi.exec(v))
.reduce((a, b) => {
if (b && b[1]) {
a += b[1];
}
return a;
}, '');
if (result_name) {
break;
}
}
}
if (result_name) {
attachment.name = decodeURI(result_name);
}
const ct = headers['Content-Type'] || headers['Content-type'];
if (ct) {
attachment.contentType = ct;
}
const cd = headers['Content-Disposition'];
if (cd) {
attachment.inline = /^\s*inline/g.test(cd);
}
attachment.data = content as Uint8Array;
attachment.data64 = decode(content as Uint8Array, charset);
result.attachments.push(attachment);
}
}
function _read(data: ParsedEmlJson): ReadedEmlJson | Error | string {
if (!data) {
return 'no data';
}
try {
const result = {} as ReadedEmlJson;
if (!data.headers) {
throw new Error("data does't has headers");
}
if (data.headers['Date']) {
result.date = new Date(data.headers['Date']);
}
if (data.headers['Subject']) {
result.subject = unquoteString(data.headers['Subject']);
}
if (data.headers['From']) {
result.from = getEmailAddress(data.headers['From']);
}
if (data.headers['To']) {
result.to = getEmailAddress(data.headers['To']);
}
if (data.headers['CC']) {
result.cc = getEmailAddress(data.headers['CC']);
}
if (data.headers['Cc']) {
result.cc = getEmailAddress(data.headers['Cc']);
}
result.headers = data.headers;
//Content mime type
let boundary: any = null;
const ct = data.headers['Content-Type'] || data.headers['Content-type'];
if (ct && /^multipart\//g.test(ct)) {
const b = getBoundary(ct);
if (b && b.length) {
boundary = b;
}
}
if (boundary && Array.isArray(data.body)) {
for (let i = 0; i < data.body.length; i++) {
const boundaryBlock = data.body[i];
if (!boundaryBlock) {
continue;
}
//Get the message content
if (typeof boundaryBlock.part === 'undefined') {
verbose && console.warn('Warning: undefined b.part');
} else if (typeof boundaryBlock.part === 'string') {
result.data = boundaryBlock.part;
} else {
if (typeof boundaryBlock.part.body === 'undefined') {
verbose && console.warn('Warning: undefined b.part.body');
} else if (typeof boundaryBlock.part.body === 'string') {
_append(boundaryBlock.part.headers, boundaryBlock.part.body, result);
} else {
// keep multipart/alternative
const currentHeaders = boundaryBlock.part.headers;
const currentHeadersContentType = currentHeaders['Content-Type'] || currentHeaders['Content-type'];
if (verbose) {
console.log(`line 969 currentHeadersContentType: ${currentHeadersContentType}`);
}
// Hasmore ?
if (currentHeadersContentType && currentHeadersContentType.indexOf('multipart') >= 0 && !result.multipartAlternative) {
result.multipartAlternative = {
'Content-Type': currentHeadersContentType,
};
}
for (let j = 0; j < boundaryBlock.part.body.length; j++) {
const selfBoundary = boundaryBlock.part.body[j];
if (typeof selfBoundary === 'string') {
result.data = selfBoundary;
continue;
}
const headers = selfBoundary.part.headers;
const content = selfBoundary.part.body;
if (Array.isArray(content)) {
(content as any).forEach((bound: any) => {
_append(bound.part.headers, bound.part.body, result);
});
} else {
_append(headers, content, result);
}
}
}
}
}
} else if (typeof data.body === 'string') {
_append(data.headers, data.body, result);
}
return result;
} catch (e) {
return e as any;
}
}
if (typeof eml === 'string') {
const parseResult = parse(eml, options as OptionOrNull);
if (typeof parseResult === 'string' || parseResult instanceof Error) {
error = parseResult;
} else {
const readResult = _read(parseResult);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
} else {
result = readResult;
}
}
} else if (typeof eml === 'object') {
const readResult = _read(eml);
if (typeof readResult === 'string' || readResult instanceof Error) {
error = readResult;
} else {
result = readResult;
}
} else {
error = new Error('Missing EML file content!');
}
callback && callback(error, result);
return error || result || new Error('read EML failed!');
}
/**
* if you need
* eml-format all api
*/
export {
getEmailAddress,
toEmailAddress,
createBoundary,
getBoundary,
getCharset,
unquoteString,
unquotePrintable,
mimeDecode,
Base64,
convert,
encode,
decode,
completeBoundary,
parse as parseEml,
read as readEml,
build as buildEml,
GB2312UTF8 as GBKUTF8,
};
// const GBKUTF8 = GB2312UTF8;
// const parseEml = parse;
// const readEml = read;
// const buildEml = build;
export interface KeyValue extends Object {
[k: string]: any;
}
export interface EmailAddress {
name: string;
email: string;
}
/**
* parse result
*/
export interface ParsedEmlJson {
headers: EmlHeaders;
body?: string | (BoundaryConvertedData | null)[];
}
/**
* read result
*/
export interface ReadedEmlJson {
date: Date | string;
subject: string;
from: EmailAddress | EmailAddress[] | null;
to: EmailAddress | EmailAddress[] | null;
cc?: EmailAddress | EmailAddress[] | null;
headers: EmlHeaders;
multipartAlternative?: {
'Content-Type': string;
};
text?: string;
textheaders?: BoundaryHeaders;
html?: string;
htmlheaders?: BoundaryHeaders;
attachments?: Attachment[];
// data not be build
// if have EMl can find `data`, maybe I will know how to do
data?: string;
}
/**
* Attachment file
*/
export interface Attachment {
name: string;
contentType: string;
inline: boolean;
data: string | Uint8Array;
data64: string;
filename?: string;
mimeType?: string;
id?: string;
cid?: string;
}
/**
* EML headers
* @description `MIME-Version`, `Accept-Language`, `Content-Language` and `Content-Type` shuld Must exist when to build a EML file
*/
export interface EmlHeaders extends KeyValue {
Date?: string;
Subject?: string;
From?: string;
To?: string;
Cc?: string;
CC?: string;
'Content-Disposition'?: string | null;
'Content-Type'?: string | null;
'Content-Transfer-Encoding'?: string;
'MIME-Version'?: string;
'Content-ID'?: string;
// zh-CN, en-US
'Accept-Language'?: string;
// zh-CN
'Content-Language'?: string;
// Why not all ?
// OutLook is follows
'Content-type'?: string | null;
'Content-transfer-encoding'?: string;
}
export interface Options {
headersOnly: boolean;
}
/**
* encode is not realized yet
*/
export interface BuildOptions extends Options {
encode?: boolean; // Not realized yet
}
export type CallbackFn<T> = (error: any, result?: T) => void;
export type OptionOrNull = Options | null;
/**
* BoundaryRawData
*/
export interface BoundaryRawData {
boundary: string;
lines: string[];
}
/**
* Convert BoundaryRawData result
*/
export interface BoundaryConvertedData {
boundary: string;
part: {
headers: BoundaryHeaders;
body: string | Array<BoundaryConvertedData | string>;
};
}
export interface BoundaryHeaders extends KeyValue {
'Content-Type': string;
'Content-Transfer-Encoding'?: string;
'Content-Disposition'?: string;
}
import { decode } from './charset';
/**
* Gets the boundary name
* @param contentType - string
*/
export function getBoundary(contentType: string) {
const match = /(?:B|b)oundary=(?:'|")?(.+?)(?:'|")?(\s*;[\s\S]*)?$/g.exec(contentType);
return match ? match[1] : undefined;
}
//Gets the character encoding name for iconv, e.g. 'iso-8859-2' -> 'iso88592'
export function getCharsetName(charset: string) {
return charset.toLowerCase().replace(/[^0-9a-z]/g, '');
}
//Generates a random id
export function guid() {
return 'xxxxxxxxxxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function(c) {
const r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
.replace('-', '');
}
//Word-wrap the string 's' to 'i' chars per row
export function wrap(s: string, i: number) {
const a = [] as any[];
do {
a.push(s.substring(0, i));
} while ((s = s.substring(i, s.length)) != '');
return a.join('\r\n');
}
/**
* Decodes mime encoded string to an unicode string
*
* @param {String} str Mime encoded string
* @param {String} [fromCharset='UTF-8'] Source encoding
* @return {String} Decoded unicode string
*/
export function mimeDecode(str = '', fromCharset = 'UTF-8') {
const encodedBytesCount = (str.match(/=[\da-fA-F]{2}/g) || []).length;
let buffer = new Uint8Array(str.length - encodedBytesCount * 2);
for (let i = 0, len = str.length, bufferPos = 0; i < len; i++) {
let hex = str.substr(i + 1, 2);
const chr = str.charAt(i);
if (chr === '=' && hex && /[\da-fA-F]{2}/.test(hex)) {
buffer[bufferPos++] = parseInt(hex, 16);
i += 2;
} else {
buffer[bufferPos++] = chr.charCodeAt(0);
}
}
return decode(buffer, fromCharset);
}
/**
* adjust string Or Error
* @param param
*/
export function isStringOrError(param: any) {
return typeof param === 'string' || param instanceof Error;
}
/**
* converting strings from gbk to utf-8
*/
export const GB2312UTF8 = {
Dig2Dec: function(s: string) {
let retV = 0;
if (s.length == 4) {
for (let i = 0; i < 4; i++) {
retV += eval(s.charAt(i)) * Math.pow(2, 3 - i);
}
return retV;
}
return -1;
},
Hex2Utf8: function(s: string) {
let retS = '';
let tempS = '';
let ss = '';
if (s.length == 16) {
tempS = '1110' + s.substring(0, 4);
tempS += '10' + s.substring(4, 10);
tempS += '10' + s.substring(10, 16);
let sss = '0123456789ABCDEF';
for (let i = 0; i < 3; i++) {
retS += '%';
ss = tempS.substring(i * 8, (eval(i.toString()) + 1) * 8);
retS += sss.charAt(this.Dig2Dec(ss.substring(0, 4)));
retS += sss.charAt(this.Dig2Dec(ss.substring(4, 8)));
}
return retS;
}
return '';
},
Dec2Dig: function(n1: number) {
let s = '';
let n2 = 0;
for (let i = 0; i < 4; i++) {
n2 = Math.pow(2, 3 - i);
if (n1 >= n2) {
s += '1';
n1 = n1 - n2;
} else {
s += '0';
}
}
return s;
},
Str2Hex: function(s: string) {
let c = '';
let n;
let ss = '0123456789ABCDEF';
let digS = '';
for (let i = 0; i < s.length; i++) {
c = s.charAt(i);
n = ss.indexOf(c);
digS += this.Dec2Dig(eval(n.toString()));
}
return digS;
},
GB2312ToUTF8: function(s1: string) {
let s = escape(s1);
let sa = s.split('%');
let retV = '';
if (sa[0] != '') {
retV = sa[0];
}
for (let i = 1; i < sa.length; i++) {
if (sa[i].substring(0, 1) == 'u') {
retV += this.Hex2Utf8(this.Str2Hex(sa[i].substring(1, 5)));
if (sa[i].length) {
retV += sa[i].substring(5);
}
} else {
retV += unescape('%' + sa[i]);
if (sa[i].length) {
retV += sa[i].substring(5);
}
}
}
return retV;
},
UTF8ToGB2312: function(str1: string) {
let substr = '';
let a = '';
let b = '';
let c = '';
let i = -1;
i = str1.indexOf('%');
if (i == -1) {
return str1;
}
while (i != -1) {
if (i < 3) {
substr = substr + str1.substr(0, i - 1);
str1 = str1.substr(i + 1, str1.length - i);
a = str1.substr(0, 2);
str1 = str1.substr(2, str1.length - 2);
if ((parseInt('0x' + a) & 0x80) === 0) {
substr = substr + String.fromCharCode(parseInt('0x' + a));
} else if ((parseInt('0x' + a) & 0xe0) === 0xc0) {
//two byte
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
let widechar = (parseInt('0x' + a) & 0x1f) << 6;
widechar = widechar | (parseInt('0x' + b) & 0x3f);
substr = substr + String.fromCharCode(widechar);
} else {
b = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
c = str1.substr(1, 2);
str1 = str1.substr(3, str1.length - 3);
let widechar = (parseInt('0x' + a) & 0x0f) << 12;
widechar = widechar | ((parseInt('0x' + b) & 0x3f) << 6);
widechar = widechar | (parseInt('0x' + c) & 0x3f);
substr = substr + String.fromCharCode(widechar);
}
} else {
substr = substr + str1.substring(0, i);
str1 = str1.substring(i);
}
i = str1.indexOf('%');
}
return substr + str1;
},
};