eml-parse-js
Advanced tools
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 |
+1
-1
@@ -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 |
+37
-53
| { | ||
| "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" | ||
| } |
-1424
| 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|(")/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 }); | ||
| }); |
-1425
| '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|(")/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; |
-1401
| 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|(")/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 }; |
-1428
| (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|(")/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'], | ||
| })); |
-1428
| (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|(")/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; | ||
| } |
-979
| /** | ||
| * @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|(")/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; |
-117
| 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; | ||
| } |
-198
| 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; | ||
| }, | ||
| }; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
2881110
802.37%14
-17.65%19977
124.69%21
-41.67%13
-27.78%12
-33.33%1
Infinity%