├── .babelrc ├── .editorconfig ├── .gitignore ├── .jshintrc ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── dist ├── mimeparser.js └── timezones.js ├── package.json ├── scripts └── build.sh ├── src ├── mimeparser-unit.js ├── mimeparser.js └── timezones.js └── testutils.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | max_line_length = null 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .DS_Store 4 | package-lock.json 5 | 6 | # VIM Swap Files 7 | [._]*.s[a-v][a-z] 8 | [._]*.sw[a-p] 9 | [._]s[a-v][a-z] 10 | [._]sw[a-p] 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 4, 3 | "strict": true, 4 | "globalstrict": true, 5 | "node": true, 6 | "browser": true, 7 | "nonew": true, 8 | "curly": true, 9 | "eqeqeq": true, 10 | "indent": false, 11 | "immed": true, 12 | "newcap": true, 13 | "regexp": true, 14 | "evil": true, 15 | "eqnull": true, 16 | "expr": true, 17 | "trailing": true, 18 | "undef": true, 19 | "unused": true, 20 | 21 | "globals": { 22 | "TextEncoder": true, 23 | "TextDecoder": true, 24 | "mimefuncs": true, 25 | "console": true, 26 | "define": true, 27 | "describe": true, 28 | "it": true, 29 | "beforeEach": true, 30 | "afterEach": true, 31 | "window": true, 32 | "mocha": true, 33 | "mochaPhantomJS": true, 34 | "importScripts": true, 35 | "postMessage": true, 36 | "before": true, 37 | "self": true 38 | } 39 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - lts/* 5 | notifications: 6 | email: 7 | recipients: 8 | - felix.hammerl@gmail.com 9 | script: 10 | - npm test 11 | deploy: 12 | provider: npm 13 | email: felix.hammerl+emailjs-deployment-user@gmail.com 14 | api_key: 15 | secure: Miz/qgi5CTT/VhvfJTJY6SIy1qmWQxLCAeiPkXDYfzhOqXwpBA3bMwilqeyiuQr0QvEUWYQaR58rZ/krl890M5e8lcqUB8S2qoKMeX/gqNSp7yOapk9nynCVUVZoLvGxm74vFI1A8lL19lCGxYBylf+bCx57wkdiiBMhnPBjrNQ= 16 | on: 17 | tags: true 18 | all_branches: true 19 | condition: "$TRAVIS_TAG =~ ^v[0-9]+\\.[0-9]+\\.[0-9]+" 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run ES6 Tests", 6 | "type": "node", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 10 | "stopOnEntry": false, 11 | "args": [ 12 | "./src/*-unit.js", 13 | "--require", "babel-register", "testutils.js", 14 | "--reporter", "spec", 15 | "--no-timeouts" 16 | ], 17 | "runtimeArgs": [ 18 | "--nolazy" 19 | ], 20 | "sourceMaps": true 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Andris Reinman 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # emailjs-mime-parser 2 | 3 | ## HELP WANTED 4 | 5 | Felix is not actively maintaining this library anymore. But this is the only IMAP client for JS that I am aware of, so I feel this library still has its value. Please let me know if you're interested in helping out, either via email or open an issue about that. 6 | 7 | The work that's on the horizon is: 8 | 9 | * Adding features as per requests 10 | * Refactor to allow streaming and cut down memory consumption 11 | * Stay up to date with developments in the IMAP protocol 12 | * Maintenance of the other related emailjs libraries 13 | * Maintenance and update of [emailjs.org](https://emailjs.org) 14 | 15 | [![Greenkeeper badge](https://badges.greenkeeper.io/emailjs/emailjs-mime-parser.svg)](https://greenkeeper.io/) [![Build Status](https://travis-ci.org/emailjs/emailjs-mime-parser.png?branch=master)](https://travis-ci.org/emailjs/emailjs-mime-parser) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![ES6+](https://camo.githubusercontent.com/567e52200713e0f0c05a5238d91e1d096292b338/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f65732d362b2d627269676874677265656e2e737667)](https://kangax.github.io/compat-table/es6/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 16 | 17 | Parse a mime tree, no magic included. This is supposed to be a "low level" mime parsing module. No magic is performed on the data (eg. no joining HTML parts etc.). All body data is emitted out as Typed Arrays, so no need to perform any base64 or quoted printable decoding by yourself. Text parts are decoded to UTF-8 if needed. In addition, line terminators are preserved which permits the functionality of verifying a message signature. 18 | 19 | ## Usage 20 | 21 | ``` 22 | npm install --save emailjs-mime-parser 23 | ``` 24 | 25 | ```javascript 26 | import parse from 'emailjs-mime-parser' 27 | 28 | parse(String) -> MimeNode 29 | ``` 30 | 31 | ### MimeNode 32 | 33 | A MimeNode represents a MIME tree. 34 | 35 | ``` 36 | MimeNode 37 | | 38 | +----> childNodes -> [MimeNode] 39 | +----> content -> Uint8Array 40 | +----> bodyStructure -> String 41 | ``` 42 | 43 | ``` 44 | MimeNode.childNodes -> [MimeNode] 45 | ``` 46 | 47 | The child MIME nodes are stored in the `childNodes` array. 48 | 49 | ``` 50 | MimeNode.content -> Uint8Array 51 | ``` 52 | 53 | The content of the specific node is stored in `this.content` as Uint8Array. All body data is emitted as Typed Arrays, so no need to perform any base64 or quoted printable decoding by yourself. Text parts are decoded to UTF-8 if needed. 54 | 55 | **message/rfc822** is automatically parsed if the mime part does not have a `Content-Disposition: attachment` header, otherwise it will be emitted as a regular attachment (as one long Uint8Array value). 56 | 57 | 58 | ``` 59 | MimeNode.bodyStructure -> String 60 | ``` 61 | 62 | Bodystructure is the original raw message stripped of bodies and multipart preambles. MIME stores like to store the bodystructure of MIME content in raw (loss-less) form, to later run through a MIME parser to answer IMAP or WebDAV type queries. 63 | 64 | ## License 65 | 66 | The MIT license 67 | 68 | Copyright (c) 2013 Andris Reinman 69 | 70 | Permission is hereby granted, free of charge, to any person obtaining a copy 71 | of this software and associated documentation files (the "Software"), to deal 72 | in the Software without restriction, including without limitation the rights 73 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 74 | copies of the Software, and to permit persons to whom the Software is 75 | furnished to do so, subject to the following conditions: 76 | 77 | The above copyright notice and this permission notice shall be included in 78 | all copies or substantial portions of the Software. 79 | 80 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 81 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 82 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 83 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 84 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 85 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 86 | THE SOFTWARE. 87 | -------------------------------------------------------------------------------- /dist/mimeparser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.MimeNode = exports.NodeCounter = undefined; 7 | 8 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 9 | 10 | exports.default = parse; 11 | 12 | var _ramda = require('ramda'); 13 | 14 | var _timezones = require('./timezones'); 15 | 16 | var _timezones2 = _interopRequireDefault(_timezones); 17 | 18 | var _emailjsMimeCodec = require('emailjs-mime-codec'); 19 | 20 | var _textEncoding = require('text-encoding'); 21 | 22 | var _emailjsAddressparser = require('emailjs-addressparser'); 23 | 24 | var _emailjsAddressparser2 = _interopRequireDefault(_emailjsAddressparser); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 29 | 30 | /* 31 | * Counts MIME nodes to prevent memory exhaustion attacks (CWE-400) 32 | * see: https://snyk.io/vuln/npm:emailjs-mime-parser:20180625 33 | */ 34 | var MAXIMUM_NUMBER_OF_MIME_NODES = 999; 35 | 36 | var NodeCounter = exports.NodeCounter = function () { 37 | function NodeCounter() { 38 | _classCallCheck(this, NodeCounter); 39 | 40 | this.count = 0; 41 | } 42 | 43 | _createClass(NodeCounter, [{ 44 | key: 'bump', 45 | value: function bump() { 46 | if (++this.count > MAXIMUM_NUMBER_OF_MIME_NODES) { 47 | throw new Error('Maximum number of MIME nodes exceeded!'); 48 | } 49 | } 50 | }]); 51 | 52 | return NodeCounter; 53 | }(); 54 | 55 | function forEachLine(str, callback) { 56 | var line = ''; 57 | var terminator = ''; 58 | for (var i = 0; i < str.length; i += 1) { 59 | var char = str[i]; 60 | if (char === '\r' || char === '\n') { 61 | var nextChar = str[i + 1]; 62 | terminator += char; 63 | // Detect Windows and Macintosh line terminators. 64 | if (terminator + nextChar === '\r\n' || terminator + nextChar === '\n\r') { 65 | callback(line, terminator + nextChar); 66 | line = ''; 67 | terminator = ''; 68 | i += 1; 69 | // Detect single-character terminators, like Linux or other system. 70 | } else if (terminator === '\n' || terminator === '\r') { 71 | callback(line, terminator); 72 | line = ''; 73 | terminator = ''; 74 | } 75 | } else { 76 | line += char; 77 | } 78 | } 79 | // Flush the line and terminator values if necessary; handle edge cases where MIME is generated without last line terminator. 80 | if (line !== '' || terminator !== '') { 81 | callback(line, terminator); 82 | } 83 | } 84 | 85 | function parse(chunk) { 86 | var root = new MimeNode(new NodeCounter()); 87 | var str = typeof chunk === 'string' ? chunk : String.fromCharCode.apply(null, chunk); 88 | forEachLine(str, function (line, terminator) { 89 | root.writeLine(line, terminator); 90 | }); 91 | root.finalize(); 92 | return root; 93 | } 94 | 95 | var MimeNode = exports.MimeNode = function () { 96 | function MimeNode() { 97 | var nodeCounter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new NodeCounter(); 98 | 99 | _classCallCheck(this, MimeNode); 100 | 101 | this.nodeCounter = nodeCounter; 102 | this.nodeCounter.bump(); 103 | 104 | this.header = []; // An array of unfolded header lines 105 | this.headers = {}; // An object that holds header key=value pairs 106 | this.bodystructure = ''; 107 | this.childNodes = []; // If this is a multipart or message/rfc822 mime part, the value will be converted to array and hold all child nodes for this node 108 | this.raw = ''; // Stores the raw content of this node 109 | 110 | this._state = 'HEADER'; // Current state, always starts out with HEADER 111 | this._bodyBuffer = ''; // Body buffer 112 | this._lineCount = 0; // Line counter bor the body part 113 | this._currentChild = false; // Active child node (if available) 114 | this._lineRemainder = ''; // Remainder string when dealing with base64 and qp values 115 | this._isMultipart = false; // Indicates if this is a multipart node 116 | this._multipartBoundary = false; // Stores boundary value for current multipart node 117 | this._isRfc822 = false; // Indicates if this is a message/rfc822 node 118 | } 119 | 120 | _createClass(MimeNode, [{ 121 | key: 'writeLine', 122 | value: function writeLine(line, terminator) { 123 | this.raw += line + (terminator || '\n'); 124 | 125 | if (this._state === 'HEADER') { 126 | this._processHeaderLine(line); 127 | } else if (this._state === 'BODY') { 128 | this._processBodyLine(line, terminator); 129 | } 130 | } 131 | }, { 132 | key: 'finalize', 133 | value: function finalize() { 134 | var _this = this; 135 | 136 | if (this._isRfc822) { 137 | this._currentChild.finalize(); 138 | } else { 139 | this._emitBody(); 140 | } 141 | 142 | this.bodystructure = this.childNodes.reduce(function (agg, child) { 143 | return agg + '--' + _this._multipartBoundary + '\n' + child.bodystructure; 144 | }, this.header.join('\n') + '\n\n') + (this._multipartBoundary ? '--' + this._multipartBoundary + '--\n' : ''); 145 | } 146 | }, { 147 | key: '_decodeBodyBuffer', 148 | value: function _decodeBodyBuffer() { 149 | switch (this.contentTransferEncoding.value) { 150 | case 'base64': 151 | this._bodyBuffer = (0, _emailjsMimeCodec.base64Decode)(this._bodyBuffer, this.charset); 152 | break; 153 | case 'quoted-printable': 154 | { 155 | this._bodyBuffer = this._bodyBuffer.replace(/=(\r?\n|$)/g, '').replace(/=([a-f0-9]{2})/ig, function (m, code) { 156 | return String.fromCharCode(parseInt(code, 16)); 157 | }); 158 | break; 159 | } 160 | } 161 | } 162 | 163 | /** 164 | * Processes a line in the HEADER state. It the line is empty, change state to BODY 165 | * 166 | * @param {String} line Entire input line as 'binary' string 167 | */ 168 | 169 | }, { 170 | key: '_processHeaderLine', 171 | value: function _processHeaderLine(line) { 172 | if (!line) { 173 | this._parseHeaders(); 174 | this.bodystructure += this.header.join('\n') + '\n\n'; 175 | this._state = 'BODY'; 176 | return; 177 | } 178 | 179 | if (line.match(/^\s/) && this.header.length) { 180 | this.header[this.header.length - 1] += '\n' + line; 181 | } else { 182 | this.header.push(line); 183 | } 184 | } 185 | 186 | /** 187 | * Joins folded header lines and calls Content-Type and Transfer-Encoding processors 188 | */ 189 | 190 | }, { 191 | key: '_parseHeaders', 192 | value: function _parseHeaders() { 193 | for (var hasBinary = false, i = 0, len = this.header.length; i < len; i++) { 194 | var value = this.header[i].split(':'); 195 | var key = (value.shift() || '').trim().toLowerCase(); 196 | value = (value.join(':') || '').replace(/\n/g, '').trim(); 197 | 198 | if (value.match(/[\u0080-\uFFFF]/)) { 199 | if (!this.charset) { 200 | hasBinary = true; 201 | } 202 | // use default charset at first and if the actual charset is resolved, the conversion is re-run 203 | value = (0, _emailjsMimeCodec.decode)((0, _emailjsMimeCodec.convert)(str2arr(value), this.charset || 'iso-8859-1')); 204 | } 205 | 206 | this.headers[key] = (this.headers[key] || []).concat([this._parseHeaderValue(key, value)]); 207 | 208 | if (!this.charset && key === 'content-type') { 209 | this.charset = this.headers[key][this.headers[key].length - 1].params.charset; 210 | } 211 | 212 | if (hasBinary && this.charset) { 213 | // reset values and start over once charset has been resolved and 8bit content has been found 214 | hasBinary = false; 215 | this.headers = {}; 216 | i = -1; // next iteration has i == 0 217 | } 218 | } 219 | 220 | this.fetchContentType(); 221 | this._processContentTransferEncoding(); 222 | } 223 | 224 | /** 225 | * Parses single header value 226 | * @param {String} key Header key 227 | * @param {String} value Value for the key 228 | * @return {Object} parsed header 229 | */ 230 | 231 | }, { 232 | key: '_parseHeaderValue', 233 | value: function _parseHeaderValue(key, value) { 234 | var parsedValue = void 0; 235 | var isAddress = false; 236 | 237 | switch (key) { 238 | case 'content-type': 239 | case 'content-transfer-encoding': 240 | case 'content-disposition': 241 | case 'dkim-signature': 242 | parsedValue = (0, _emailjsMimeCodec.parseHeaderValue)(value); 243 | break; 244 | case 'from': 245 | case 'sender': 246 | case 'to': 247 | case 'reply-to': 248 | case 'cc': 249 | case 'bcc': 250 | case 'abuse-reports-to': 251 | case 'errors-to': 252 | case 'return-path': 253 | case 'delivered-to': 254 | isAddress = true; 255 | parsedValue = { 256 | value: [].concat((0, _emailjsAddressparser2.default)(value) || []) 257 | }; 258 | break; 259 | case 'date': 260 | parsedValue = { 261 | value: this._parseDate(value) 262 | }; 263 | break; 264 | default: 265 | parsedValue = { 266 | value: value 267 | }; 268 | } 269 | parsedValue.initial = value; 270 | 271 | this._decodeHeaderCharset(parsedValue, { isAddress: isAddress }); 272 | 273 | return parsedValue; 274 | } 275 | 276 | /** 277 | * Checks if a date string can be parsed. Falls back replacing timezone 278 | * abbrevations with timezone values. Bogus timezones default to UTC. 279 | * 280 | * @param {String} str Date header 281 | * @returns {String} UTC date string if parsing succeeded, otherwise returns input value 282 | */ 283 | 284 | }, { 285 | key: '_parseDate', 286 | value: function _parseDate() { 287 | var str = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 288 | 289 | var date = new Date(str.trim().replace(/\b[a-z]+$/i, function (tz) { 290 | return _timezones2.default[tz.toUpperCase()] || '+0000'; 291 | })); 292 | return date.toString() !== 'Invalid Date' ? date.toUTCString().replace(/GMT/, '+0000') : str; 293 | } 294 | }, { 295 | key: '_decodeHeaderCharset', 296 | value: function _decodeHeaderCharset(parsed) { 297 | var _this2 = this; 298 | 299 | var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, 300 | isAddress = _ref.isAddress; 301 | 302 | // decode default value 303 | if (typeof parsed.value === 'string') { 304 | parsed.value = (0, _emailjsMimeCodec.mimeWordsDecode)(parsed.value); 305 | } 306 | 307 | // decode possible params 308 | Object.keys(parsed.params || {}).forEach(function (key) { 309 | if (typeof parsed.params[key] === 'string') { 310 | parsed.params[key] = (0, _emailjsMimeCodec.mimeWordsDecode)(parsed.params[key]); 311 | } 312 | }); 313 | 314 | // decode addresses 315 | if (isAddress && Array.isArray(parsed.value)) { 316 | parsed.value.forEach(function (addr) { 317 | if (addr.name) { 318 | addr.name = (0, _emailjsMimeCodec.mimeWordsDecode)(addr.name); 319 | if (Array.isArray(addr.group)) { 320 | _this2._decodeHeaderCharset({ value: addr.group }, { isAddress: true }); 321 | } 322 | } 323 | }); 324 | } 325 | 326 | return parsed; 327 | } 328 | 329 | /** 330 | * Parses Content-Type value and selects following actions. 331 | */ 332 | 333 | }, { 334 | key: 'fetchContentType', 335 | value: function fetchContentType() { 336 | var defaultValue = (0, _emailjsMimeCodec.parseHeaderValue)('text/plain'); 337 | this.contentType = (0, _ramda.pathOr)(defaultValue, ['headers', 'content-type', '0'])(this); 338 | this.contentType.value = (this.contentType.value || '').toLowerCase().trim(); 339 | this.contentType.type = this.contentType.value.split('/').shift() || 'text'; 340 | 341 | if (this.contentType.params && this.contentType.params.charset && !this.charset) { 342 | this.charset = this.contentType.params.charset; 343 | } 344 | 345 | if (this.contentType.type === 'multipart' && this.contentType.params.boundary) { 346 | this.childNodes = []; 347 | this._isMultipart = this.contentType.value.split('/').pop() || 'mixed'; 348 | this._multipartBoundary = this.contentType.params.boundary; 349 | } 350 | 351 | /** 352 | * For attachment (inline/regular) if charset is not defined and attachment is non-text/*, 353 | * then default charset to binary. 354 | * Refer to issue: https://github.com/emailjs/emailjs-mime-parser/issues/18 355 | */ 356 | var defaultContentDispositionValue = (0, _emailjsMimeCodec.parseHeaderValue)(''); 357 | var contentDisposition = (0, _ramda.pathOr)(defaultContentDispositionValue, ['headers', 'content-disposition', '0'])(this); 358 | var isAttachment = (contentDisposition.value || '').toLowerCase().trim() === 'attachment'; 359 | var isInlineAttachment = (contentDisposition.value || '').toLowerCase().trim() === 'inline'; 360 | if ((isAttachment || isInlineAttachment) && this.contentType.type !== 'text' && !this.charset) { 361 | this.charset = 'binary'; 362 | } 363 | 364 | if (this.contentType.value === 'message/rfc822' && !isAttachment) { 365 | /** 366 | * Parse message/rfc822 only if the mime part is not marked with content-disposition: attachment, 367 | * otherwise treat it like a regular attachment 368 | */ 369 | this._currentChild = new MimeNode(this.nodeCounter); 370 | this.childNodes = [this._currentChild]; 371 | this._isRfc822 = true; 372 | } 373 | } 374 | 375 | /** 376 | * Parses Content-Transfer-Encoding value to see if the body needs to be converted 377 | * before it can be emitted 378 | */ 379 | 380 | }, { 381 | key: '_processContentTransferEncoding', 382 | value: function _processContentTransferEncoding() { 383 | var defaultValue = (0, _emailjsMimeCodec.parseHeaderValue)('7bit'); 384 | this.contentTransferEncoding = (0, _ramda.pathOr)(defaultValue, ['headers', 'content-transfer-encoding', '0'])(this); 385 | this.contentTransferEncoding.value = (0, _ramda.pathOr)('', ['contentTransferEncoding', 'value'])(this).toLowerCase().trim(); 386 | } 387 | 388 | /** 389 | * Processes a line in the BODY state. If this is a multipart or rfc822 node, 390 | * passes line value to child nodes. 391 | * 392 | * @param {String} line Entire input line as 'binary' string 393 | * @param {String} terminator The line terminator detected by parser 394 | */ 395 | 396 | }, { 397 | key: '_processBodyLine', 398 | value: function _processBodyLine(line, terminator) { 399 | if (this._isMultipart) { 400 | if (line === '--' + this._multipartBoundary) { 401 | this.bodystructure += line + '\n'; 402 | if (this._currentChild) { 403 | this._currentChild.finalize(); 404 | } 405 | this._currentChild = new MimeNode(this.nodeCounter); 406 | this.childNodes.push(this._currentChild); 407 | } else if (line === '--' + this._multipartBoundary + '--') { 408 | this.bodystructure += line + '\n'; 409 | if (this._currentChild) { 410 | this._currentChild.finalize(); 411 | } 412 | this._currentChild = false; 413 | } else if (this._currentChild) { 414 | this._currentChild.writeLine(line, terminator); 415 | } else { 416 | // Ignore multipart preamble 417 | } 418 | } else if (this._isRfc822) { 419 | this._currentChild.writeLine(line, terminator); 420 | } else { 421 | this._lineCount++; 422 | 423 | switch (this.contentTransferEncoding.value) { 424 | case 'base64': 425 | this._bodyBuffer += line + terminator; 426 | break; 427 | case 'quoted-printable': 428 | { 429 | var curLine = this._lineRemainder + line + terminator; 430 | var match = curLine.match(/=[a-f0-9]{0,1}$/i); 431 | if (match) { 432 | this._lineRemainder = match[0]; 433 | curLine = curLine.substr(0, curLine.length - this._lineRemainder.length); 434 | } else { 435 | this._lineRemainder = ''; 436 | } 437 | this._bodyBuffer += curLine; 438 | break; 439 | } 440 | case '7bit': 441 | case '8bit': 442 | default: 443 | this._bodyBuffer += line + terminator; 444 | break; 445 | } 446 | } 447 | } 448 | 449 | /** 450 | * Emits a chunk of the body 451 | */ 452 | 453 | }, { 454 | key: '_emitBody', 455 | value: function _emitBody() { 456 | this._decodeBodyBuffer(); 457 | if (this._isMultipart || !this._bodyBuffer) { 458 | return; 459 | } 460 | 461 | this._processFlowedText(); 462 | this.content = str2arr(this._bodyBuffer); 463 | this._processHtmlText(); 464 | this._bodyBuffer = ''; 465 | } 466 | }, { 467 | key: '_processFlowedText', 468 | value: function _processFlowedText() { 469 | var isText = /^text\/(plain|html)$/i.test(this.contentType.value); 470 | var isFlowed = /^flowed$/i.test((0, _ramda.pathOr)('', ['contentType', 'params', 'format'])(this)); 471 | if (!isText || !isFlowed) return; 472 | 473 | var delSp = /^yes$/i.test(this.contentType.params.delsp); 474 | var bodyBuffer = ''; 475 | 476 | forEachLine(this._bodyBuffer, function (line, terminator) { 477 | // remove soft linebreaks after space symbols. 478 | // delsp adds spaces to text to be able to fold it. 479 | // these spaces can be removed once the text is unfolded 480 | var endsWithSpace = / $/.test(line); 481 | var isBoundary = /(^|\n)-- $/.test(line); 482 | 483 | bodyBuffer += (delSp ? line.replace(/[ ]+$/, '') : line) + (endsWithSpace && !isBoundary ? '' : terminator); 484 | }); 485 | 486 | this._bodyBuffer = bodyBuffer.replace(/^ /gm, ''); // remove whitespace stuffing http://tools.ietf.org/html/rfc3676#section-4.4 487 | } 488 | }, { 489 | key: '_processHtmlText', 490 | value: function _processHtmlText() { 491 | var contentDisposition = this.headers['content-disposition'] && this.headers['content-disposition'][0] || (0, _emailjsMimeCodec.parseHeaderValue)(''); 492 | var isHtml = /^text\/(plain|html)$/i.test(this.contentType.value); 493 | var isAttachment = /^attachment$/i.test(contentDisposition.value); 494 | if (isHtml && !isAttachment) { 495 | if (!this.charset && /^text\/html$/i.test(this.contentType.value)) { 496 | this.charset = this.detectHTMLCharset(this._bodyBuffer); 497 | } 498 | 499 | // decode "binary" string to an unicode string 500 | if (!/^utf[-_]?8$/i.test(this.charset)) { 501 | this.content = (0, _emailjsMimeCodec.convert)(str2arr(this._bodyBuffer), this.charset || 'iso-8859-1'); 502 | } else if (this.contentTransferEncoding.value === 'base64') { 503 | this.content = utf8Str2arr(this._bodyBuffer); 504 | } 505 | 506 | // override charset for text nodes 507 | this.charset = this.contentType.params.charset = 'utf-8'; 508 | } 509 | } 510 | 511 | /** 512 | * Detect charset from a html file 513 | * 514 | * @param {String} html Input HTML 515 | * @returns {String} Charset if found or undefined 516 | */ 517 | 518 | }, { 519 | key: 'detectHTMLCharset', 520 | value: function detectHTMLCharset(html) { 521 | var charset = void 0, 522 | input = void 0; 523 | 524 | html = html.replace(/\r?\n|\r/g, ' '); 525 | var meta = html.match(/]*?>/i); 526 | if (meta) { 527 | input = meta[0]; 528 | } 529 | 530 | if (input) { 531 | charset = input.match(/charset\s?=\s?([a-zA-Z\-_:0-9]*);?/); 532 | if (charset) { 533 | charset = (charset[1] || '').trim().toLowerCase(); 534 | } 535 | } 536 | 537 | meta = html.match(//\s]+)/i); 538 | if (!charset && meta) { 539 | charset = (meta[1] || '').trim().toLowerCase(); 540 | } 541 | 542 | return charset; 543 | } 544 | }]); 545 | 546 | return MimeNode; 547 | }(); 548 | 549 | var str2arr = function str2arr(str) { 550 | return new Uint8Array(str.split('').map(function (char) { 551 | return char.charCodeAt(0); 552 | })); 553 | }; 554 | var utf8Str2arr = function utf8Str2arr(str) { 555 | return new _textEncoding.TextEncoder('utf-8').encode(str); 556 | }; 557 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9taW1lcGFyc2VyLmpzIl0sIm5hbWVzIjpbInBhcnNlIiwiTUFYSU1VTV9OVU1CRVJfT0ZfTUlNRV9OT0RFUyIsIk5vZGVDb3VudGVyIiwiY291bnQiLCJFcnJvciIsImZvckVhY2hMaW5lIiwic3RyIiwiY2FsbGJhY2siLCJsaW5lIiwidGVybWluYXRvciIsImkiLCJsZW5ndGgiLCJjaGFyIiwibmV4dENoYXIiLCJjaHVuayIsInJvb3QiLCJNaW1lTm9kZSIsIlN0cmluZyIsImZyb21DaGFyQ29kZSIsImFwcGx5Iiwid3JpdGVMaW5lIiwiZmluYWxpemUiLCJub2RlQ291bnRlciIsImJ1bXAiLCJoZWFkZXIiLCJoZWFkZXJzIiwiYm9keXN0cnVjdHVyZSIsImNoaWxkTm9kZXMiLCJyYXciLCJfc3RhdGUiLCJfYm9keUJ1ZmZlciIsIl9saW5lQ291bnQiLCJfY3VycmVudENoaWxkIiwiX2xpbmVSZW1haW5kZXIiLCJfaXNNdWx0aXBhcnQiLCJfbXVsdGlwYXJ0Qm91bmRhcnkiLCJfaXNSZmM4MjIiLCJfcHJvY2Vzc0hlYWRlckxpbmUiLCJfcHJvY2Vzc0JvZHlMaW5lIiwiX2VtaXRCb2R5IiwicmVkdWNlIiwiYWdnIiwiY2hpbGQiLCJqb2luIiwiY29udGVudFRyYW5zZmVyRW5jb2RpbmciLCJ2YWx1ZSIsImNoYXJzZXQiLCJyZXBsYWNlIiwibSIsImNvZGUiLCJwYXJzZUludCIsIl9wYXJzZUhlYWRlcnMiLCJtYXRjaCIsInB1c2giLCJoYXNCaW5hcnkiLCJsZW4iLCJzcGxpdCIsImtleSIsInNoaWZ0IiwidHJpbSIsInRvTG93ZXJDYXNlIiwic3RyMmFyciIsImNvbmNhdCIsIl9wYXJzZUhlYWRlclZhbHVlIiwicGFyYW1zIiwiZmV0Y2hDb250ZW50VHlwZSIsIl9wcm9jZXNzQ29udGVudFRyYW5zZmVyRW5jb2RpbmciLCJwYXJzZWRWYWx1ZSIsImlzQWRkcmVzcyIsIl9wYXJzZURhdGUiLCJpbml0aWFsIiwiX2RlY29kZUhlYWRlckNoYXJzZXQiLCJkYXRlIiwiRGF0ZSIsInRpbWV6b25lIiwidHoiLCJ0b1VwcGVyQ2FzZSIsInRvU3RyaW5nIiwidG9VVENTdHJpbmciLCJwYXJzZWQiLCJPYmplY3QiLCJrZXlzIiwiZm9yRWFjaCIsIkFycmF5IiwiaXNBcnJheSIsImFkZHIiLCJuYW1lIiwiZ3JvdXAiLCJkZWZhdWx0VmFsdWUiLCJjb250ZW50VHlwZSIsInR5cGUiLCJib3VuZGFyeSIsInBvcCIsImRlZmF1bHRDb250ZW50RGlzcG9zaXRpb25WYWx1ZSIsImNvbnRlbnREaXNwb3NpdGlvbiIsImlzQXR0YWNobWVudCIsImlzSW5saW5lQXR0YWNobWVudCIsImN1ckxpbmUiLCJzdWJzdHIiLCJfZGVjb2RlQm9keUJ1ZmZlciIsIl9wcm9jZXNzRmxvd2VkVGV4dCIsImNvbnRlbnQiLCJfcHJvY2Vzc0h0bWxUZXh0IiwiaXNUZXh0IiwidGVzdCIsImlzRmxvd2VkIiwiZGVsU3AiLCJkZWxzcCIsImJvZHlCdWZmZXIiLCJlbmRzV2l0aFNwYWNlIiwiaXNCb3VuZGFyeSIsImlzSHRtbCIsImRldGVjdEhUTUxDaGFyc2V0IiwidXRmOFN0cjJhcnIiLCJodG1sIiwiaW5wdXQiLCJtZXRhIiwiVWludDhBcnJheSIsIm1hcCIsImNoYXJDb2RlQXQiLCJUZXh0RW5jb2RlciIsImVuY29kZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O2tCQW9Ed0JBLEs7O0FBcER4Qjs7QUFDQTs7OztBQUNBOztBQUNBOztBQUNBOzs7Ozs7OztBQUVBOzs7O0FBSUEsSUFBTUMsK0JBQStCLEdBQXJDOztJQUNhQyxXLFdBQUFBLFc7QUFDWCx5QkFBZTtBQUFBOztBQUNiLFNBQUtDLEtBQUwsR0FBYSxDQUFiO0FBQ0Q7Ozs7MkJBQ087QUFDTixVQUFJLEVBQUUsS0FBS0EsS0FBUCxHQUFlRiw0QkFBbkIsRUFBaUQ7QUFDL0MsY0FBTSxJQUFJRyxLQUFKLENBQVUsd0NBQVYsQ0FBTjtBQUNEO0FBQ0Y7Ozs7OztBQUdILFNBQVNDLFdBQVQsQ0FBc0JDLEdBQXRCLEVBQTJCQyxR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QUFnQ25ILEdBQWhDLENBQVA7QUFBQSxDQUFwQiIsImZpbGUiOiJtaW1lcGFyc2VyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgcGF0aE9yIH0gZnJvbSAncmFtZGEnXG5pbXBvcnQgdGltZXpvbmUgZnJvbSAnLi90aW1lem9uZXMnXG5pbXBvcnQgeyBkZWNvZGUsIGJhc2U2NERlY29kZSwgY29udmVydCwgcGFyc2VIZWFkZXJWYWx1ZSwgbWltZVdvcmRzRGVjb2RlIH0gZnJvbSAnZW1haWxqcy1taW1lLWNvZGVjJ1xuaW1wb3J0IHsgVGV4dEVuY29kZXIgfSBmcm9tICd0ZXh0LWVuY29kaW5nJ1xuaW1wb3J0IHBhcnNlQWRkcmVzcyBmcm9tICdlbWFpbGpzLWFkZHJlc3NwYXJzZXInXG5cbi8qXG4gKiBDb3VudHMgTUlNRSBub2RlcyB0byBwcmV2ZW50IG1lbW9yeSBleGhhdXN0aW9uIGF0dGFja3MgKENXRS00MDApXG4gKiBzZWU6IGh0dHBzOi8vc255ay5pby92dWxuL25wbTplbWFpbGpzLW1pbWUtcGFyc2VyOjIwMTgwNjI1XG4gKi9cbmNvbnN0IE1BWElNVU1fTlVNQkVSX09GX01JTUVfTk9ERVMgPSA5OTlcbmV4cG9ydCBjbGFzcyBOb2RlQ291bnRlciB7XG4gIGNvbnN0cnVjdG9yICgpIHtcbiAgICB0aGlzLmNvdW50ID0gMFxuICB9XG4gIGJ1bXAgKCkge1xuICAgIGlmICgrK3RoaXMuY291bnQgPiBNQVhJTVVNX05VTUJFUl9PRl9NSU1FX05PREVTKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ01heGltdW0gbnVtYmVyIG9mIE1JTUUgbm9kZXMgZXhjZWVkZWQhJylcbiAgICB9XG4gIH1cbn1cblxuZnVuY3Rpb24gZm9yRWFjaExpbmUgKHN0ciwgY2FsbGJhY2spIHtcbiAgbGV0IGxpbmUgPSAnJ1xuICBsZXQgdGVybWluYXRvciA9ICcnXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgc3RyLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgY29uc3QgY2hhciA9IHN0cltpXVxuICAgIGlmIChjaGFyID09PSAnXFxyJyB8fCBjaGFyID09PSAnXFxuJykge1xuICAgICAgY29uc3QgbmV4dENoYXIgPSBzdHJbaSArIDFdXG4gICAgICB0ZXJtaW5hdG9yICs9IGNoYXJcbiAgICAgIC8vIERldGVjdCBXaW5kb3dzIGFuZCBNYWNpbnRvc2ggbGluZSB0ZXJtaW5hdG9ycy5cbiAgICAgIGlmICgodGVybWluYXRvciArIG5leHRDaGFyKSA9PT0gJ1xcclxcbicgfHwgKHRlcm1pbmF0b3IgKyBuZXh0Q2hhcikgPT09ICdcXG5cXHInKSB7XG4gICAgICAgIGNhbGxiYWNrKGxpbmUsIHRlcm1pbmF0b3IgKyBuZXh0Q2hhcilcbiAgICAgICAgbGluZSA9ICcnXG4gICAgICAgIHRlcm1pbmF0b3IgPSAnJ1xuICAgICAgICBpICs9IDFcbiAgICAgIC8vIERldGVjdCBzaW5nbGUtY2hhcmFjdGVyIHRlcm1pbmF0b3JzLCBsaWtlIExpbnV4IG9yIG90aGVyIHN5c3RlbS5cbiAgICAgIH0gZWxzZSBpZiAodGVybWluYXRvciA9PT0gJ1xcbicgfHwgdGVybWluYXRvciA9PT0gJ1xccicpIHtcbiAgICAgICAgY2FsbGJhY2sobGluZSwgdGVybWluYXRvcilcbiAgICAgICAgbGluZSA9ICcnXG4gICAgICAgIHRlcm1pbmF0b3IgPSAnJ1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBsaW5lICs9IGNoYXJcbiAgICB9XG4gIH1cbiAgLy8gRmx1c2ggdGhlIGxpbmUgYW5kIHRlcm1pbmF0b3IgdmFsdWVzIGlmIG5lY2Vzc2FyeTsgaGFuZGxlIGVkZ2UgY2FzZXMgd2hlcmUgTUlNRSBpcyBnZW5lcmF0ZWQgd2l0aG91dCBsYXN0IGxpbmUgdGVybWluYXRvci5cbiAgaWYgKGxpbmUgIT09ICcnIHx8IHRlcm1pbmF0b3IgIT09ICcnKSB7XG4gICAgY2FsbGJhY2sobGluZSwgdGVybWluYXRvcilcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBwYXJzZSAoY2h1bmspIHtcbiAgY29uc3Qgcm9vdCA9IG5ldyBNaW1lTm9kZShuZXcgTm9kZUNvdW50ZXIoKSlcbiAgY29uc3Qgc3RyID0gdHlwZW9mIGNodW5rID09PSAnc3RyaW5nJyA/IGNodW5rIDogU3RyaW5nLmZyb21DaGFyQ29kZS5hcHBseShudWxsLCBjaHVuaylcbiAgZm9yRWFjaExpbmUoc3RyLCBmdW5jdGlvbiAobGluZSwgdGVybWluYXRvcikge1xuICAgIHJvb3Qud3JpdGVMaW5lKGxpbmUsIHRlcm1pbmF0b3IpXG4gIH0pXG4gIHJvb3QuZmluYWxpemUoKVxuICByZXR1cm4gcm9vdFxufVxuXG5leHBvcnQgY2xhc3MgTWltZU5vZGUge1xuICBjb25zdHJ1Y3RvciAobm9kZUNvdW50ZXIgPSBuZXcgTm9kZUNvdW50ZXIoKSkge1xuICAgIHRoaXMubm9kZUNvdW50ZXIgPSBub2RlQ291bnRlclxuICAgIHRoaXMubm9kZUNvdW50ZXIuYnVtcCgpXG5cbiAgICB0aGlzLmhlYWRlciA9IFtdIC8vIEFuIGFycmF5IG9mIHVuZm9sZGVkIGhlYWRlciBsaW5lc1xuICAgIHRoaXMuaGVhZGVycyA9IHt9IC8vIEFuIG9iamVjdCB0aGF0IGhvbGRzIGhlYWRlciBrZXk9dmFsdWUgcGFpcnNcbiAgICB0aGlzLmJvZHlzdHJ1Y3R1cmUgPSAnJ1xuICAgIHRoaXMuY2hpbGROb2RlcyA9IFtdIC8vIElmIHRoaXMgaXMgYSBtdWx0aXBhcnQgb3IgbWVzc2FnZS9yZmM4MjIgbWltZSBwYXJ0LCB0aGUgdmFsdWUgd2lsbCBiZSBjb252ZXJ0ZWQgdG8gYXJyYXkgYW5kIGhvbGQgYWxsIGNoaWxkIG5vZGVzIGZvciB0aGlzIG5vZGVcbiAgICB0aGlzLnJhdyA9ICcnIC8vIFN0b3JlcyB0aGUgcmF3IGNvbnRlbnQgb2YgdGhpcyBub2RlXG5cbiAgICB0aGlzLl9zdGF0ZSA9ICdIRUFERVInIC8vIEN1cnJlbnQgc3RhdGUsIGFsd2F5cyBzdGFydHMgb3V0IHdpdGggSEVBREVSXG4gICAgdGhpcy5fYm9keUJ1ZmZlciA9ICcnIC8vIEJvZHkgYnVmZmVyXG4gICAgdGhpcy5fbGluZUNvdW50ID0gMCAvLyBMaW5lIGNvdW50ZXIgYm9yIHRoZSBib2R5IHBhcnRcbiAgICB0aGlzLl9jdXJyZW50Q2hpbGQgPSBmYWxzZSAvLyBBY3RpdmUgY2hpbGQgbm9kZSAoaWYgYXZhaWxhYmxlKVxuICAgIHRoaXMuX2xpbmVSZW1haW5kZXIgPSAnJyAvLyBSZW1haW5kZXIgc3RyaW5nIHdoZW4gZGVhbGluZyB3aXRoIGJhc2U2NCBhbmQgcXAgdmFsdWVzXG4gICAgdGhpcy5faXNNdWx0aXBhcnQgPSBmYWxzZSAvLyBJbmRpY2F0ZXMgaWYgdGhpcyBpcyBhIG11bHRpcGFydCBub2RlXG4gICAgdGhpcy5fbXVsdGlwYXJ0Qm91bmRhcnkgPSBmYWxzZSAvLyBTdG9yZXMgYm91bmRhcnkgdmFsdWUgZm9yIGN1cnJlbnQgbXVsdGlwYXJ0IG5vZGVcbiAgICB0aGlzLl9pc1JmYzgyMiA9IGZhbHNlIC8vIEluZGljYXRlcyBpZiB0aGlzIGlzIGEgbWVzc2FnZS9yZmM4MjIgbm9kZVxuICB9XG5cbiAgd3JpdGVMaW5lIChsaW5lLCB0ZXJtaW5hdG9yKSB7XG4gICAgdGhpcy5yYXcgKz0gbGluZSArICh0ZXJtaW5hdG9yIHx8ICdcXG4nKVxuXG4gICAgaWYgKHRoaXMuX3N0YXRlID09PSAnSEVBREVSJykge1xuICAgICAgdGhpcy5fcHJvY2Vzc0hlYWRlckxpbmUobGluZSlcbiAgICB9IGVsc2UgaWYgKHRoaXMuX3N0YXRlID09PSAnQk9EWScpIHtcbiAgICAgIHRoaXMuX3Byb2Nlc3NCb2R5TGluZShsaW5lLCB0ZXJtaW5hdG9yKVxuICAgIH1cbiAgfVxuXG4gIGZpbmFsaXplICgpIHtcbiAgICBpZiAodGhpcy5faXNSZmM4MjIpIHtcbiAgICAgIHRoaXMuX2N1cnJlbnRDaGlsZC5maW5hbGl6ZSgpXG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX2VtaXRCb2R5KClcbiAgICB9XG5cbiAgICB0aGlzLmJvZHlzdHJ1Y3R1cmUgPSB0aGlzLmNoaWxkTm9kZXNcbiAgICAgIC5yZWR1Y2UoKGFnZywgY2hpbGQpID0+IGFnZyArICctLScgKyB0aGlzLl9tdWx0aXBhcnRCb3VuZGFyeSArICdcXG4nICsgY2hpbGQuYm9keXN0cnVjdHVyZSwgdGhpcy5oZWFkZXIuam9pbignXFxuJykgKyAnXFxuXFxuJykgK1xuICAgICAgKHRoaXMuX211bHRpcGFydEJvdW5kYXJ5ID8gJy0tJyArIHRoaXMuX211bHRpcGFydEJvdW5kYXJ5ICsgJy0tXFxuJyA6ICcnKVxuICB9XG5cbiAgX2RlY29kZUJvZHlCdWZmZXIgKCkge1xuICAgIHN3aXRjaCAodGhpcy5jb250ZW50VHJhbnNmZXJFbmNvZGluZy52YWx1ZSkge1xuICAgICAgY2FzZSAnYmFzZTY0JzpcbiAgICAgICAgdGhpcy5fYm9keUJ1ZmZlciA9IGJhc2U2NERlY29kZSh0aGlzLl9ib2R5QnVmZmVyLCB0aGlzLmNoYXJzZXQpXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdxdW90ZWQtcHJpbnRhYmxlJzoge1xuICAgICAgICB0aGlzLl9ib2R5QnVmZmVyID0gdGhpcy5fYm9keUJ1ZmZlclxuICAgICAgICAgIC5yZXBsYWNlKC89KFxccj9cXG58JCkvZywgJycpXG4gICAgICAgICAgLnJlcGxhY2UoLz0oW2EtZjAtOV17Mn0pL2lnLCAobSwgY29kZSkgPT4gU3RyaW5nLmZyb21DaGFyQ29kZShwYXJzZUludChjb2RlLCAxNikpKVxuICAgICAgICBicmVha1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQcm9jZXNzZXMgYSBsaW5lIGluIHRoZSBIRUFERVIgc3RhdGUuIEl0IHRoZSBsaW5lIGlzIGVtcHR5LCBjaGFuZ2Ugc3RhdGUgdG8gQk9EWVxuICAgKlxuICAgKiBAcGFyYW0ge1N0cmluZ30gbGluZSBFbnRpcmUgaW5wdXQgbGluZSBhcyAnYmluYXJ5JyBzdHJpbmdcbiAgICovXG4gIF9wcm9jZXNzSGVhZGVyTGluZSAobGluZSkge1xuICAgIGlmICghbGluZSkge1xuICAgICAgdGhpcy5fcGFyc2VIZWFkZXJzKClcbiAgICAgIHRoaXMuYm9keXN0cnVjdHVyZSArPSB0aGlzLmhlYWRlci5qb2luKCdcXG4nKSArICdcXG5cXG4nXG4gICAgICB0aGlzLl9zdGF0ZSA9ICdCT0RZJ1xuICAgICAgcmV0dXJuXG4gICAgfVxuXG4gICAgaWYgKGxpbmUubWF0Y2goL15cXHMvKSAmJiB0aGlzLmhlYWRlci5sZW5ndGgpIHtcbiAgICAgIHRoaXMuaGVhZGVyW3RoaXMuaGVhZGVyLmxlbmd0aCAtIDFdICs9ICdcXG4nICsgbGluZVxuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLmhlYWRlci5wdXNoKGxpbmUpXG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIEpvaW5zIGZvbGRlZCBoZWFkZXIgbGluZXMgYW5kIGNhbGxzIENvbnRlbnQtVHlwZSBhbmQgVHJhbnNmZXItRW5jb2RpbmcgcHJvY2Vzc29yc1xuICAgKi9cbiAgX3BhcnNlSGVhZGVycyAoKSB7XG4gICAgZm9yIChsZXQgaGFzQmluYXJ5ID0gZmFsc2UsIGkgPSAwLCBsZW4gPSB0aGlzLmhlYWRlci5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgbGV0IHZhbHVlID0gdGhpcy5oZWFkZXJbaV0uc3BsaXQoJzonKVxuICAgICAgY29uc3Qga2V5ID0gKHZhbHVlLnNoaWZ0KCkgfHwgJycpLnRyaW0oKS50b0xvd2VyQ2FzZSgpXG4gICAgICB2YWx1ZSA9ICh2YWx1ZS5qb2luKCc6JykgfHwgJycpLnJlcGxhY2UoL1xcbi9nLCAnJykudHJpbSgpXG5cbiAgICAgIGlmICh2YWx1ZS5tYXRjaCgvW1xcdTAwODAtXFx1RkZGRl0vKSkge1xuICAgICAgICBpZiAoIXRoaXMuY2hhcnNldCkge1xuICAgICAgICAgIGhhc0JpbmFyeSA9IHRydWVcbiAgICAgICAgfVxuICAgICAgICAvLyB1c2UgZGVmYXVsdCBjaGFyc2V0IGF0IGZpcnN0IGFuZCBpZiB0aGUgYWN0dWFsIGNoYXJzZXQgaXMgcmVzb2x2ZWQsIHRoZSBjb252ZXJzaW9uIGlzIHJlLXJ1blxuICAgICAgICB2YWx1ZSA9IGRlY29kZShjb252ZXJ0KHN0cjJhcnIodmFsdWUpLCB0aGlzLmNoYXJzZXQgfHwgJ2lzby04ODU5LTEnKSlcbiAgICAgIH1cblxuICAgICAgdGhpcy5oZWFkZXJzW2tleV0gPSAodGhpcy5oZWFkZXJzW2tleV0gfHwgW10pLmNvbmNhdChbdGhpcy5fcGFyc2VIZWFkZXJWYWx1ZShrZXksIHZhbHVlKV0pXG5cbiAgICAgIGlmICghdGhpcy5jaGFyc2V0ICYmIGtleSA9PT0gJ2NvbnRlbnQtdHlwZScpIHtcbiAgICAgICAgdGhpcy5jaGFyc2V0ID0gdGhpcy5oZWFkZXJzW2tleV1bdGhpcy5oZWFkZXJzW2tleV0ubGVuZ3RoIC0gMV0ucGFyYW1zLmNoYXJzZXRcbiAgICAgIH1cblxuICAgICAgaWYgKGhhc0JpbmFyeSAmJiB0aGlzLmNoYXJzZXQpIHtcbiAgICAgICAgLy8gcmVzZXQgdmFsdWVzIGFuZCBzdGFydCBvdmVyIG9uY2UgY2hhcnNldCBoYXMgYmVlbiByZXNvbHZlZCBhbmQgOGJpdCBjb250ZW50IGhhcyBiZWVuIGZvdW5kXG4gICAgICAgIGhhc0JpbmFyeSA9IGZhbHNlXG4gICAgICAgIHRoaXMuaGVhZGVycyA9IHt9XG4gICAgICAgIGkgPSAtMSAvLyBuZXh0IGl0ZXJhdGlvbiBoYXMgaSA9PSAwXG4gICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5mZXRjaENvbnRlbnRUeXBlKClcbiAgICB0aGlzLl9wcm9jZXNzQ29udGVudFRyYW5zZmVyRW5jb2RpbmcoKVxuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlcyBzaW5nbGUgaGVhZGVyIHZhbHVlXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBrZXkgSGVhZGVyIGtleVxuICAgKiBAcGFyYW0ge1N0cmluZ30gdmFsdWUgVmFsdWUgZm9yIHRoZSBrZXlcbiAgICogQHJldHVybiB7T2JqZWN0fSBwYXJzZWQgaGVhZGVyXG4gICAqL1xuICBfcGFyc2VIZWFkZXJWYWx1ZSAoa2V5LCB2YWx1ZSkge1xuICAgIGxldCBwYXJzZWRWYWx1ZVxuICAgIGxldCBpc0FkZHJlc3MgPSBmYWxzZVxuXG4gICAgc3dpdGNoIChrZXkpIHtcbiAgICAgIGNhc2UgJ2NvbnRlbnQtdHlwZSc6XG4gICAgICBjYXNlICdjb250ZW50LXRyYW5zZmVyLWVuY29kaW5nJzpcbiAgICAgIGNhc2UgJ2NvbnRlbnQtZGlzcG9zaXRpb24nOlxuICAgICAgY2FzZSAnZGtpbS1zaWduYXR1cmUnOlxuICAgICAgICBwYXJzZWRWYWx1ZSA9IHBhcnNlSGVhZGVyVmFsdWUodmFsdWUpXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdmcm9tJzpcbiAgICAgIGNhc2UgJ3NlbmRlcic6XG4gICAgICBjYXNlICd0byc6XG4gICAgICBjYXNlICdyZXBseS10byc6XG4gICAgICBjYXNlICdjYyc6XG4gICAgICBjYXNlICdiY2MnOlxuICAgICAgY2FzZSAnYWJ1c2UtcmVwb3J0cy10byc6XG4gICAgICBjYXNlICdlcnJvcnMtdG8nOlxuICAgICAgY2FzZSAncmV0dXJuLXBhdGgnOlxuICAgICAgY2FzZSAnZGVsaXZlcmVkLXRvJzpcbiAgICAgICAgaXNBZGRyZXNzID0gdHJ1ZVxuICAgICAgICBwYXJzZWRWYWx1ZSA9IHtcbiAgICAgICAgICB2YWx1ZTogW10uY29uY2F0KHBhcnNlQWRkcmVzcyh2YWx1ZSkgfHwgW10pXG4gICAgICAgIH1cbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2RhdGUnOlxuICAgICAgICBwYXJzZWRWYWx1ZSA9IHtcbiAgICAgICAgICB2YWx1ZTogdGhpcy5fcGFyc2VEYXRlKHZhbHVlKVxuICAgICAgICB9XG4gICAgICAgIGJyZWFrXG4gICAgICBkZWZhdWx0OlxuICAgICAgICBwYXJzZWRWYWx1ZSA9IHtcbiAgICAgICAgICB2YWx1ZTogdmFsdWVcbiAgICAgICAgfVxuICAgIH1cbiAgICBwYXJzZWRWYWx1ZS5pbml0aWFsID0gdmFsdWVcblxuICAgIHRoaXMuX2RlY29kZUhlYWRlckNoYXJzZXQocGFyc2VkVmFsdWUsIHsgaXNBZGRyZXNzIH0pXG5cbiAgICByZXR1cm4gcGFyc2VkVmFsdWVcbiAgfVxuXG4gIC8qKlxuICAgKiBDaGVja3MgaWYgYSBkYXRlIHN0cmluZyBjYW4gYmUgcGFyc2VkLiBGYWxscyBiYWNrIHJlcGxhY2luZyB0aW1lem9uZVxuICAgKiBhYmJyZXZhdGlvbnMgd2l0aCB0aW1lem9uZSB2YWx1ZXMuIEJvZ3VzIHRpbWV6b25lcyBkZWZhdWx0IHRvIFVUQy5cbiAgICpcbiAgICogQHBhcmFtIHtTdHJpbmd9IHN0ciBEYXRlIGhlYWRlclxuICAgKiBAcmV0dXJucyB7U3RyaW5nfSBVVEMgZGF0ZSBzdHJpbmcgaWYgcGFyc2luZyBzdWNjZWVkZWQsIG90aGVyd2lzZSByZXR1cm5zIGlucHV0IHZhbHVlXG4gICAqL1xuICBfcGFyc2VEYXRlIChzdHIgPSAnJykge1xuICAgIGNvbnN0IGRhdGUgPSBuZXcgRGF0ZShzdHIudHJpbSgpLnJlcGxhY2UoL1xcYlthLXpdKyQvaSwgdHogPT4gdGltZXpvbmVbdHoudG9VcHBlckNhc2UoKV0gfHwgJyswMDAwJykpXG4gICAgcmV0dXJuIChkYXRlLnRvU3RyaW5nKCkgIT09ICdJbnZhbGlkIERhdGUnKSA/IGRhdGUudG9VVENTdHJpbmcoKS5yZXBsYWNlKC9HTVQvLCAnKzAwMDAnKSA6IHN0clxuICB9XG5cbiAgX2RlY29kZUhlYWRlckNoYXJzZXQgKHBhcnNlZCwgeyBpc0FkZHJlc3MgfSA9IHt9KSB7XG4gICAgLy8gZGVjb2RlIGRlZmF1bHQgdmFsdWVcbiAgICBpZiAodHlwZW9mIHBhcnNlZC52YWx1ZSA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHBhcnNlZC52YWx1ZSA9IG1pbWVXb3Jkc0RlY29kZShwYXJzZWQudmFsdWUpXG4gICAgfVxuXG4gICAgLy8gZGVjb2RlIHBvc3NpYmxlIHBhcmFtc1xuICAgIE9iamVjdC5rZXlzKHBhcnNlZC5wYXJhbXMgfHwge30pLmZvckVhY2goZnVuY3Rpb24gKGtleSkge1xuICAgICAgaWYgKHR5cGVvZiBwYXJzZWQucGFyYW1zW2tleV0gPT09ICdzdHJpbmcnKSB7XG4gICAgICAgIHBhcnNlZC5wYXJhbXNba2V5XSA9IG1pbWVXb3Jkc0RlY29kZShwYXJzZWQucGFyYW1zW2tleV0pXG4gICAgICB9XG4gICAgfSlcblxuICAgIC8vIGRlY29kZSBhZGRyZXNzZXNcbiAgICBpZiAoaXNBZGRyZXNzICYmIEFycmF5LmlzQXJyYXkocGFyc2VkLnZhbHVlKSkge1xuICAgICAgcGFyc2VkLnZhbHVlLmZvckVhY2goYWRkciA9PiB7XG4gICAgICAgIGlmIChhZGRyLm5hbWUpIHtcbiAgICAgICAgICBhZGRyLm5hbWUgPSBtaW1lV29yZHNEZWNvZGUoYWRkci5uYW1lKVxuICAgICAgICAgIGlmIChBcnJheS5pc0FycmF5KGFkZHIuZ3JvdXApKSB7XG4gICAgICAgICAgICB0aGlzLl9kZWNvZGVIZWFkZXJDaGFyc2V0KHsgdmFsdWU6IGFkZHIuZ3JvdXAgfSwgeyBpc0FkZHJlc3M6IHRydWUgfSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgfVxuXG4gICAgcmV0dXJuIHBhcnNlZFxuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlcyBDb250ZW50LVR5cGUgdmFsdWUgYW5kIHNlbGVjdHMgZm9sbG93aW5nIGFjdGlvbnMuXG4gICAqL1xuICBmZXRjaENvbnRlbnRUeXBlICgpIHtcbiAgICBjb25zdCBkZWZhdWx0VmFsdWUgPSBwYXJzZUhlYWRlclZhbHVlKCd0ZXh0L3BsYWluJylcbiAgICB0aGlzLmNvbnRlbnRUeXBlID0gcGF0aE9yKGRlZmF1bHRWYWx1ZSwgWydoZWFkZXJzJywgJ2NvbnRlbnQtdHlwZScsICcwJ10pKHRoaXMpXG4gICAgdGhpcy5jb250ZW50VHlwZS52YWx1ZSA9ICh0aGlzLmNvbnRlbnRUeXBlLnZhbHVlIHx8ICcnKS50b0xvd2VyQ2FzZSgpLnRyaW0oKVxuICAgIHRoaXMuY29udGVudFR5cGUudHlwZSA9ICh0aGlzLmNvbnRlbnRUeXBlLnZhbHVlLnNwbGl0KCcvJykuc2hpZnQoKSB8fCAndGV4dCcpXG5cbiAgICBpZiAodGhpcy5jb250ZW50VHlwZS5wYXJhbXMgJiYgdGhpcy5jb250ZW50VHlwZS5wYXJhbXMuY2hhcnNldCAmJiAhdGhpcy5jaGFyc2V0KSB7XG4gICAgICB0aGlzLmNoYXJzZXQgPSB0aGlzLmNvbnRlbnRUeXBlLnBhcmFtcy5jaGFyc2V0XG4gICAgfVxuXG4gICAgaWYgKHRoaXMuY29udGVudFR5cGUudHlwZSA9PT0gJ211bHRpcGFydCcgJiYgdGhpcy5jb250ZW50VHlwZS5wYXJhbXMuYm91bmRhcnkpIHtcbiAgICAgIHRoaXMuY2hpbGROb2RlcyA9IFtdXG4gICAgICB0aGlzLl9pc011bHRpcGFydCA9ICh0aGlzLmNvbnRlbnRUeXBlLnZhbHVlLnNwbGl0KCcvJykucG9wKCkgfHwgJ21peGVkJylcbiAgICAgIHRoaXMuX211bHRpcGFydEJvdW5kYXJ5ID0gdGhpcy5jb250ZW50VHlwZS5wYXJhbXMuYm91bmRhcnlcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBGb3IgYXR0YWNobWVudCAoaW5saW5lL3JlZ3VsYXIpIGlmIGNoYXJzZXQgaXMgbm90IGRlZmluZWQgYW5kIGF0dGFjaG1lbnQgaXMgbm9uLXRleHQvKixcbiAgICAgKiB0aGVuIGRlZmF1bHQgY2hhcnNldCB0byBiaW5hcnkuXG4gICAgICogUmVmZXIgdG8gaXNzdWU6IGh0dHBzOi8vZ2l0aHViLmNvbS9lbWFpbGpzL2VtYWlsanMtbWltZS1wYXJzZXIvaXNzdWVzLzE4XG4gICAgICovXG4gICAgY29uc3QgZGVmYXVsdENvbnRlbnREaXNwb3NpdGlvblZhbHVlID0gcGFyc2VIZWFkZXJWYWx1ZSgnJylcbiAgICBjb25zdCBjb250ZW50RGlzcG9zaXRpb24gPSBwYXRoT3IoZGVmYXVsdENvbnRlbnREaXNwb3NpdGlvblZhbHVlLCBbJ2hlYWRlcnMnLCAnY29udGVudC1kaXNwb3NpdGlvbicsICcwJ10pKHRoaXMpXG4gICAgY29uc3QgaXNBdHRhY2htZW50ID0gKGNvbnRlbnREaXNwb3NpdGlvbi52YWx1ZSB8fCAnJykudG9Mb3dlckNhc2UoKS50cmltKCkgPT09ICdhdHRhY2htZW50J1xuICAgIGNvbnN0IGlzSW5saW5lQXR0YWNobWVudCA9IChjb250ZW50RGlzcG9zaXRpb24udmFsdWUgfHwgJycpLnRvTG93ZXJDYXNlKCkudHJpbSgpID09PSAnaW5saW5lJ1xuICAgIGlmICgoaXNBdHRhY2htZW50IHx8IGlzSW5saW5lQXR0YWNobWVudCkgJiYgdGhpcy5jb250ZW50VHlwZS50eXBlICE9PSAndGV4dCcgJiYgIXRoaXMuY2hhcnNldCkge1xuICAgICAgdGhpcy5jaGFyc2V0ID0gJ2JpbmFyeSdcbiAgICB9XG5cbiAgICBpZiAodGhpcy5jb250ZW50VHlwZS52YWx1ZSA9PT0gJ21lc3NhZ2UvcmZjODIyJyAmJiAhaXNBdHRhY2htZW50KSB7XG4gICAgICAvKipcbiAgICAgICAqIFBhcnNlIG1lc3NhZ2UvcmZjODIyIG9ubHkgaWYgdGhlIG1pbWUgcGFydCBpcyBub3QgbWFya2VkIHdpdGggY29udGVudC1kaXNwb3NpdGlvbjogYXR0YWNobWVudCxcbiAgICAgICAqIG90aGVyd2lzZSB0cmVhdCBpdCBsaWtlIGEgcmVndWxhciBhdHRhY2htZW50XG4gICAgICAgKi9cbiAgICAgIHRoaXMuX2N1cnJlbnRDaGlsZCA9IG5ldyBNaW1lTm9kZSh0aGlzLm5vZGVDb3VudGVyKVxuICAgICAgdGhpcy5jaGlsZE5vZGVzID0gW3RoaXMuX2N1cnJlbnRDaGlsZF1cbiAgICAgIHRoaXMuX2lzUmZjODIyID0gdHJ1ZVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJzZXMgQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZyB2YWx1ZSB0byBzZWUgaWYgdGhlIGJvZHkgbmVlZHMgdG8gYmUgY29udmVydGVkXG4gICAqIGJlZm9yZSBpdCBjYW4gYmUgZW1pdHRlZFxuICAgKi9cbiAgX3Byb2Nlc3NDb250ZW50VHJhbnNmZXJFbmNvZGluZyAoKSB7XG4gICAgY29uc3QgZGVmYXVsdFZhbHVlID0gcGFyc2VIZWFkZXJWYWx1ZSgnN2JpdCcpXG4gICAgdGhpcy5jb250ZW50VHJhbnNmZXJFbmNvZGluZyA9IHBhdGhPcihkZWZhdWx0VmFsdWUsIFsnaGVhZGVycycsICdjb250ZW50LXRyYW5zZmVyLWVuY29kaW5nJywgJzAnXSkodGhpcylcbiAgICB0aGlzLmNvbnRlbnRUcmFuc2ZlckVuY29kaW5nLnZhbHVlID0gcGF0aE9yKCcnLCBbJ2NvbnRlbnRUcmFuc2ZlckVuY29kaW5nJywgJ3ZhbHVlJ10pKHRoaXMpLnRvTG93ZXJDYXNlKCkudHJpbSgpXG4gIH1cblxuICAvKipcbiAgICogUHJvY2Vzc2VzIGEgbGluZSBpbiB0aGUgQk9EWSBzdGF0ZS4gSWYgdGhpcyBpcyBhIG11bHRpcGFydCBvciByZmM4MjIgbm9kZSxcbiAgICogcGFzc2VzIGxpbmUgdmFsdWUgdG8gY2hpbGQgbm9kZXMuXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBsaW5lIEVudGlyZSBpbnB1dCBsaW5lIGFzICdiaW5hcnknIHN0cmluZ1xuICAgKiBAcGFyYW0ge1N0cmluZ30gdGVybWluYXRvciBUaGUgbGluZSB0ZXJtaW5hdG9yIGRldGVjdGVkIGJ5IHBhcnNlclxuICAgKi9cbiAgX3Byb2Nlc3NCb2R5TGluZSAobGluZSwgdGVybWluYXRvcikge1xuICAgIGlmICh0aGlzLl9pc011bHRpcGFydCkge1xuICAgICAgaWYgKGxpbmUgPT09ICctLScgKyB0aGlzLl9tdWx0aXBhcnRCb3VuZGFyeSkge1xuICAgICAgICB0aGlzLmJvZHlzdHJ1Y3R1cmUgKz0gbGluZSArICdcXG4nXG4gICAgICAgIGlmICh0aGlzLl9jdXJyZW50Q2hpbGQpIHtcbiAgICAgICAgICB0aGlzLl9jdXJyZW50Q2hpbGQuZmluYWxpemUoKVxuICAgICAgICB9XG4gICAgICAgIHRoaXMuX2N1cnJlbnRDaGlsZCA9IG5ldyBNaW1lTm9kZSh0aGlzLm5vZGVDb3VudGVyKVxuICAgICAgICB0aGlzLmNoaWxkTm9kZXMucHVzaCh0aGlzLl9jdXJyZW50Q2hpbGQpXG4gICAgICB9IGVsc2UgaWYgKGxpbmUgPT09ICctLScgKyB0aGlzLl9tdWx0aXBhcnRCb3VuZGFyeSArICctLScpIHtcbiAgICAgICAgdGhpcy5ib2R5c3RydWN0dXJlICs9IGxpbmUgKyAnXFxuJ1xuICAgICAgICBpZiAodGhpcy5fY3VycmVudENoaWxkKSB7XG4gICAgICAgICAgdGhpcy5fY3VycmVudENoaWxkLmZpbmFsaXplKClcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9jdXJyZW50Q2hpbGQgPSBmYWxzZVxuICAgICAgfSBlbHNlIGlmICh0aGlzLl9jdXJyZW50Q2hpbGQpIHtcbiAgICAgICAgdGhpcy5fY3VycmVudENoaWxkLndyaXRlTGluZShsaW5lLCB0ZXJtaW5hdG9yKVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgLy8gSWdub3JlIG11bHRpcGFydCBwcmVhbWJsZVxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAodGhpcy5faXNSZmM4MjIpIHtcbiAgICAgIHRoaXMuX2N1cnJlbnRDaGlsZC53cml0ZUxpbmUobGluZSwgdGVybWluYXRvcilcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5fbGluZUNvdW50KytcblxuICAgICAgc3dpdGNoICh0aGlzLmNvbnRlbnRUcmFuc2ZlckVuY29kaW5nLnZhbHVlKSB7XG4gICAgICAgIGNhc2UgJ2Jhc2U2NCc6XG4gICAgICAgICAgdGhpcy5fYm9keUJ1ZmZlciArPSBsaW5lICsgdGVybWluYXRvclxuICAgICAgICAgIGJyZWFrXG4gICAgICAgIGNhc2UgJ3F1b3RlZC1wcmludGFibGUnOiB7XG4gICAgICAgICAgbGV0IGN1ckxpbmUgPSB0aGlzLl9saW5lUmVtYWluZGVyICsgbGluZSArIHRlcm1pbmF0b3JcbiAgICAgICAgICBjb25zdCBtYXRjaCA9IGN1ckxpbmUubWF0Y2goLz1bYS1mMC05XXswLDF9JC9pKVxuICAgICAgICAgIGlmIChtYXRjaCkge1xuICAgICAgICAgICAgdGhpcy5fbGluZVJlbWFpbmRlciA9IG1hdGNoWzBdXG4gICAgICAgICAgICBjdXJMaW5lID0gY3VyTGluZS5zdWJzdHIoMCwgY3VyTGluZS5sZW5ndGggLSB0aGlzLl9saW5lUmVtYWluZGVyLmxlbmd0aClcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5fbGluZVJlbWFpbmRlciA9ICcnXG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMuX2JvZHlCdWZmZXIgKz0gY3VyTGluZVxuICAgICAgICAgIGJyZWFrXG4gICAgICAgIH1cbiAgICAgICAgY2FzZSAnN2JpdCc6XG4gICAgICAgIGNhc2UgJzhiaXQnOlxuICAgICAgICBkZWZhdWx0OlxuICAgICAgICAgIHRoaXMuX2JvZHlCdWZmZXIgKz0gbGluZSArIHRlcm1pbmF0b3JcbiAgICAgICAgICBicmVha1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBFbWl0cyBhIGNodW5rIG9mIHRoZSBib2R5XG4gICovXG4gIF9lbWl0Qm9keSAoKSB7XG4gICAgdGhpcy5fZGVjb2RlQm9keUJ1ZmZlcigpXG4gICAgaWYgKHRoaXMuX2lzTXVsdGlwYXJ0IHx8ICF0aGlzLl9ib2R5QnVmZmVyKSB7XG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICB0aGlzLl9wcm9jZXNzRmxvd2VkVGV4dCgpXG4gICAgdGhpcy5jb250ZW50ID0gc3RyMmFycih0aGlzLl9ib2R5QnVmZmVyKVxuICAgIHRoaXMuX3Byb2Nlc3NIdG1sVGV4dCgpXG4gICAgdGhpcy5fYm9keUJ1ZmZlciA9ICcnXG4gIH1cblxuICBfcHJvY2Vzc0Zsb3dlZFRleHQgKCkge1xuICAgIGNvbnN0IGlzVGV4dCA9IC9edGV4dFxcLyhwbGFpbnxodG1sKSQvaS50ZXN0KHRoaXMuY29udGVudFR5cGUudmFsdWUpXG4gICAgY29uc3QgaXNGbG93ZWQgPSAvXmZsb3dlZCQvaS50ZXN0KHBhdGhPcignJywgWydjb250ZW50VHlwZScsICdwYXJhbXMnLCAnZm9ybWF0J10pKHRoaXMpKVxuICAgIGlmICghaXNUZXh0IHx8ICFpc0Zsb3dlZCkgcmV0dXJuXG5cbiAgICBjb25zdCBkZWxTcCA9IC9eeWVzJC9pLnRlc3QodGhpcy5jb250ZW50VHlwZS5wYXJhbXMuZGVsc3ApXG4gICAgbGV0IGJvZHlCdWZmZXIgPSAnJ1xuXG4gICAgZm9yRWFjaExpbmUodGhpcy5fYm9keUJ1ZmZlciwgZnVuY3Rpb24gKGxpbmUsIHRlcm1pbmF0b3IpIHtcbiAgICAgIC8vIHJlbW92ZSBzb2Z0IGxpbmVicmVha3MgYWZ0ZXIgc3BhY2Ugc3ltYm9scy5cbiAgICAgIC8vIGRlbHNwIGFkZHMgc3BhY2VzIHRvIHRleHQgdG8gYmUgYWJsZSB0byBmb2xkIGl0LlxuICAgICAgLy8gdGhlc2Ugc3BhY2VzIGNhbiBiZSByZW1vdmVkIG9uY2UgdGhlIHRleHQgaXMgdW5mb2xkZWRcbiAgICAgIGNvbnN0IGVuZHNXaXRoU3BhY2UgPSAvICQvLnRlc3QobGluZSlcbiAgICAgIGNvbnN0IGlzQm91bmRhcnkgPSAvKF58XFxuKS0tICQvLnRlc3QobGluZSlcblxuICAgICAgYm9keUJ1ZmZlciArPSAoZGVsU3AgPyBsaW5lLnJlcGxhY2UoL1sgXSskLywgJycpIDogbGluZSkgKyAoKGVuZHNXaXRoU3BhY2UgJiYgIWlzQm91bmRhcnkpID8gJycgOiB0ZXJtaW5hdG9yKVxuICAgIH0pXG5cbiAgICB0aGlzLl9ib2R5QnVmZmVyID0gYm9keUJ1ZmZlci5yZXBsYWNlKC9eIC9nbSwgJycpIC8vIHJlbW92ZSB3aGl0ZXNwYWNlIHN0dWZmaW5nIGh0dHA6Ly90b29scy5pZXRmLm9yZy9odG1sL3JmYzM2NzYjc2VjdGlvbi00LjRcbiAgfVxuXG4gIF9wcm9jZXNzSHRtbFRleHQgKCkge1xuICAgIGNvbnN0IGNvbnRlbnREaXNwb3NpdGlvbiA9ICh0aGlzLmhlYWRlcnNbJ2NvbnRlbnQtZGlzcG9zaXRpb24nXSAmJiB0aGlzLmhlYWRlcnNbJ2NvbnRlbnQtZGlzcG9zaXRpb24nXVswXSkgfHwgcGFyc2VIZWFkZXJWYWx1ZSgnJylcbiAgICBjb25zdCBpc0h0bWwgPSAvXnRleHRcXC8ocGxhaW58aHRtbCkkL2kudGVzdCh0aGlzLmNvbnRlbnRUeXBlLnZhbHVlKVxuICAgIGNvbnN0IGlzQXR0YWNobWVudCA9IC9eYXR0YWNobWVudCQvaS50ZXN0KGNvbnRlbnREaXNwb3NpdGlvbi52YWx1ZSlcbiAgICBpZiAoaXNIdG1sICYmICFpc0F0dGFjaG1lbnQpIHtcbiAgICAgIGlmICghdGhpcy5jaGFyc2V0ICYmIC9edGV4dFxcL2h0bWwkL2kudGVzdCh0aGlzLmNvbnRlbnRUeXBlLnZhbHVlKSkge1xuICAgICAgICB0aGlzLmNoYXJzZXQgPSB0aGlzLmRldGVjdEhUTUxDaGFyc2V0KHRoaXMuX2JvZHlCdWZmZXIpXG4gICAgICB9XG5cbiAgICAgIC8vIGRlY29kZSBcImJpbmFyeVwiIHN0cmluZyB0byBhbiB1bmljb2RlIHN0cmluZ1xuICAgICAgaWYgKCEvXnV0ZlstX10/OCQvaS50ZXN0KHRoaXMuY2hhcnNldCkpIHtcbiAgICAgICAgdGhpcy5jb250ZW50ID0gY29udmVydChzdHIyYXJyKHRoaXMuX2JvZHlCdWZmZXIpLCB0aGlzLmNoYXJzZXQgfHwgJ2lzby04ODU5LTEnKVxuICAgICAgfSBlbHNlIGlmICh0aGlzLmNvbnRlbnRUcmFuc2ZlckVuY29kaW5nLnZhbHVlID09PSAnYmFzZTY0Jykge1xuICAgICAgICB0aGlzLmNvbnRlbnQgPSB1dGY4U3RyMmFycih0aGlzLl9ib2R5QnVmZmVyKVxuICAgICAgfVxuXG4gICAgICAvLyBvdmVycmlkZSBjaGFyc2V0IGZvciB0ZXh0IG5vZGVzXG4gICAgICB0aGlzLmNoYXJzZXQgPSB0aGlzLmNvbnRlbnRUeXBlLnBhcmFtcy5jaGFyc2V0ID0gJ3V0Zi04J1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBEZXRlY3QgY2hhcnNldCBmcm9tIGEgaHRtbCBmaWxlXG4gICAqXG4gICAqIEBwYXJhbSB7U3RyaW5nfSBodG1sIElucHV0IEhUTUxcbiAgICogQHJldHVybnMge1N0cmluZ30gQ2hhcnNldCBpZiBmb3VuZCBvciB1bmRlZmluZWRcbiAgICovXG4gIGRldGVjdEhUTUxDaGFyc2V0IChodG1sKSB7XG4gICAgbGV0IGNoYXJzZXQsIGlucHV0XG5cbiAgICBodG1sID0gaHRtbC5yZXBsYWNlKC9cXHI/XFxufFxcci9nLCAnICcpXG4gICAgbGV0IG1ldGEgPSBodG1sLm1hdGNoKC88bWV0YVxccytodHRwLWVxdWl2PVtcIidcXHNdKmNvbnRlbnQtdHlwZVtePl0qPz4vaSlcbiAgICBpZiAobWV0YSkge1xuICAgICAgaW5wdXQgPSBtZXRhWzBdXG4gICAgfVxuXG4gICAgaWYgKGlucHV0KSB7XG4gICAgICBjaGFyc2V0ID0gaW5wdXQubWF0Y2goL2NoYXJzZXRcXHM/PVxccz8oW2EtekEtWlxcLV86MC05XSopOz8vKVxuICAgICAgaWYgKGNoYXJzZXQpIHtcbiAgICAgICAgY2hhcnNldCA9IChjaGFyc2V0WzFdIHx8ICcnKS50cmltKCkudG9Mb3dlckNhc2UoKVxuICAgICAgfVxuICAgIH1cblxuICAgIG1ldGEgPSBodG1sLm1hdGNoKC88bWV0YVxccytjaGFyc2V0PVtcIidcXHNdKihbXlwiJzw+L1xcc10rKS9pKVxuICAgIGlmICghY2hhcnNldCAmJiBtZXRhKSB7XG4gICAgICBjaGFyc2V0ID0gKG1ldGFbMV0gfHwgJycpLnRyaW0oKS50b0xvd2VyQ2FzZSgpXG4gICAgfVxuXG4gICAgcmV0dXJuIGNoYXJzZXRcbiAgfVxufVxuXG5jb25zdCBzdHIyYXJyID0gc3RyID0+IG5ldyBVaW50OEFycmF5KHN0ci5zcGxpdCgnJykubWFwKGNoYXIgPT4gY2hhci5jaGFyQ29kZUF0KDApKSlcbmNvbnN0IHV0ZjhTdHIyYXJyID0gc3RyID0+IG5ldyBUZXh0RW5jb2RlcigndXRmLTgnKS5lbmNvZGUoc3RyKVxuIl19 -------------------------------------------------------------------------------- /dist/timezones.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = { 7 | 'ACDT': '+1030', 8 | 'ACST': '+0930', 9 | 'ACT': '+0800', 10 | 'ADT': '-0300', 11 | 'AEDT': '+1100', 12 | 'AEST': '+1000', 13 | 'AFT': '+0430', 14 | 'AKDT': '-0800', 15 | 'AKST': '-0900', 16 | 'AMST': '-0300', 17 | 'AMT': '+0400', 18 | 'ART': '-0300', 19 | 'AST': '+0300', 20 | 'AWDT': '+0900', 21 | 'AWST': '+0800', 22 | 'AZOST': '-0100', 23 | 'AZT': '+0400', 24 | 'BDT': '+0800', 25 | 'BIOT': '+0600', 26 | 'BIT': '-1200', 27 | 'BOT': '-0400', 28 | 'BRT': '-0300', 29 | 'BST': '+0600', 30 | 'BTT': '+0600', 31 | 'CAT': '+0200', 32 | 'CCT': '+0630', 33 | 'CDT': '-0500', 34 | 'CEDT': '+0200', 35 | 'CEST': '+0200', 36 | 'CET': '+0100', 37 | 'CHADT': '+1345', 38 | 'CHAST': '+1245', 39 | 'CHOT': '+0800', 40 | 'CHST': '+1000', 41 | 'CHUT': '+1000', 42 | 'CIST': '-0800', 43 | 'CIT': '+0800', 44 | 'CKT': '-1000', 45 | 'CLST': '-0300', 46 | 'CLT': '-0400', 47 | 'COST': '-0400', 48 | 'COT': '-0500', 49 | 'CST': '-0600', 50 | 'CT': '+0800', 51 | 'CVT': '-0100', 52 | 'CWST': '+0845', 53 | 'CXT': '+0700', 54 | 'DAVT': '+0700', 55 | 'DDUT': '+1000', 56 | 'DFT': '+0100', 57 | 'EASST': '-0500', 58 | 'EAST': '-0600', 59 | 'EAT': '+0300', 60 | 'ECT': '-0500', 61 | 'EDT': '-0400', 62 | 'EEDT': '+0300', 63 | 'EEST': '+0300', 64 | 'EET': '+0200', 65 | 'EGST': '+0000', 66 | 'EGT': '-0100', 67 | 'EIT': '+0900', 68 | 'EST': '-0500', 69 | 'FET': '+0300', 70 | 'FJT': '+1200', 71 | 'FKST': '-0300', 72 | 'FKT': '-0400', 73 | 'FNT': '-0200', 74 | 'GALT': '-0600', 75 | 'GAMT': '-0900', 76 | 'GET': '+0400', 77 | 'GFT': '-0300', 78 | 'GILT': '+1200', 79 | 'GIT': '-0900', 80 | 'GMT': '+0000', 81 | 'GST': '+0400', 82 | 'GYT': '-0400', 83 | 'HADT': '-0900', 84 | 'HAEC': '+0200', 85 | 'HAST': '-1000', 86 | 'HKT': '+0800', 87 | 'HMT': '+0500', 88 | 'HOVT': '+0700', 89 | 'HST': '-1000', 90 | 'ICT': '+0700', 91 | 'IDT': '+0300', 92 | 'IOT': '+0300', 93 | 'IRDT': '+0430', 94 | 'IRKT': '+0900', 95 | 'IRST': '+0330', 96 | 'IST': '+0530', 97 | 'JST': '+0900', 98 | 'KGT': '+0600', 99 | 'KOST': '+1100', 100 | 'KRAT': '+0700', 101 | 'KST': '+0900', 102 | 'LHST': '+1030', 103 | 'LINT': '+1400', 104 | 'MAGT': '+1200', 105 | 'MART': '-0930', 106 | 'MAWT': '+0500', 107 | 'MDT': '-0600', 108 | 'MET': '+0100', 109 | 'MEST': '+0200', 110 | 'MHT': '+1200', 111 | 'MIST': '+1100', 112 | 'MIT': '-0930', 113 | 'MMT': '+0630', 114 | 'MSK': '+0400', 115 | 'MST': '-0700', 116 | 'MUT': '+0400', 117 | 'MVT': '+0500', 118 | 'MYT': '+0800', 119 | 'NCT': '+1100', 120 | 'NDT': '-0230', 121 | 'NFT': '+1130', 122 | 'NPT': '+0545', 123 | 'NST': '-0330', 124 | 'NT': '-0330', 125 | 'NUT': '-1100', 126 | 'NZDT': '+1300', 127 | 'NZST': '+1200', 128 | 'OMST': '+0700', 129 | 'ORAT': '+0500', 130 | 'PDT': '-0700', 131 | 'PET': '-0500', 132 | 'PETT': '+1200', 133 | 'PGT': '+1000', 134 | 'PHOT': '+1300', 135 | 'PHT': '+0800', 136 | 'PKT': '+0500', 137 | 'PMDT': '-0200', 138 | 'PMST': '-0300', 139 | 'PONT': '+1100', 140 | 'PST': '-0800', 141 | 'PYST': '-0300', 142 | 'PYT': '-0400', 143 | 'RET': '+0400', 144 | 'ROTT': '-0300', 145 | 'SAKT': '+1100', 146 | 'SAMT': '+0400', 147 | 'SAST': '+0200', 148 | 'SBT': '+1100', 149 | 'SCT': '+0400', 150 | 'SGT': '+0800', 151 | 'SLST': '+0530', 152 | 'SRT': '-0300', 153 | 'SST': '+0800', 154 | 'SYOT': '+0300', 155 | 'TAHT': '-1000', 156 | 'THA': '+0700', 157 | 'TFT': '+0500', 158 | 'TJT': '+0500', 159 | 'TKT': '+1300', 160 | 'TLT': '+0900', 161 | 'TMT': '+0500', 162 | 'TOT': '+1300', 163 | 'TVT': '+1200', 164 | 'UCT': '+0000', 165 | 'ULAT': '+0800', 166 | 'UTC': '+0000', 167 | 'UYST': '-0200', 168 | 'UYT': '-0300', 169 | 'UZT': '+0500', 170 | 'VET': '-0430', 171 | 'VLAT': '+1000', 172 | 'VOLT': '+0400', 173 | 'VOST': '+0600', 174 | 'VUT': '+1100', 175 | 'WAKT': '+1200', 176 | 'WAST': '+0200', 177 | 'WAT': '+0100', 178 | 'WEDT': '+0100', 179 | 'WEST': '+0100', 180 | 'WET': '+0000', 181 | 'WST': '+0800', 182 | 'YAKT': '+1000', 183 | 'YEKT': '+0600', 184 | 'Z': '+0000' 185 | }; 186 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,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Sk07QUE2SmIsU0FBTyxPQTdKTTtBQThKYixTQUFPLE9BOUpNO0FBK0piLFVBQVEsT0EvSks7QUFnS2IsU0FBTyxPQWhLTTtBQWlLYixVQUFRLE9BaktLO0FBa0tiLFNBQU8sT0FsS007QUFtS2IsU0FBTyxPQW5LTTtBQW9LYixTQUFPLE9BcEtNO0FBcUtiLFVBQVEsT0FyS0s7QUFzS2IsVUFBUSxPQXRLSztBQXVLYixVQUFRLE9BdktLO0FBd0tiLFNBQU8sT0F4S007QUF5S2IsVUFBUSxPQXpLSztBQTBLYixVQUFRLE9BMUtLO0FBMktiLFNBQU8sT0EzS007QUE0S2IsVUFBUSxPQTVLSztBQTZLYixVQUFRLE9BN0tLO0FBOEtiLFNBQU8sT0E5S007QUErS2IsU0FBTyxPQS9LTTtBQWdMYixVQUFRLE9BaExLO0FBaUxiLFVBQVEsT0FqTEs7QUFrTGIsT0FBSztBQWxMUSxDIiwiZmlsZSI6InRpbWV6b25lcy5qcyIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBkZWZhdWx0IHtcbiAgJ0FDRFQnOiAnKzEwMzAnLFxuICAnQUNTVCc6ICcrMDkzMCcsXG4gICdBQ1QnOiAnKzA4MDAnLFxuICAnQURUJzogJy0wMzAwJyxcbiAgJ0FFRFQnOiAnKzExMDAnLFxuICAnQUVTVCc6ICcrMTAwMCcsXG4gICdBRlQnOiAnKzA0MzAnLFxuICAnQUtEVCc6ICctMDgwMCcsXG4gICdBS1NUJzogJy0wOTAwJyxcbiAgJ0FNU1QnOiAnLTAzMDAnLFxuICAnQU1UJzogJyswNDAwJyxcbiAgJ0FSVCc6ICctMDMwMCcsXG4gICdBU1QnOiAnKzAzMDAnLFxuICAnQVdEVCc6ICcrMDkwMCcsXG4gICdBV1NUJzogJyswODAwJyxcbiAgJ0FaT1NUJzogJy0wMTAwJyxcbiAgJ0FaVCc6ICcrMDQwMCcsXG4gICdCRFQnOiAnKzA4MDAnLFxuICAnQklPVCc6ICcrMDYwMCcsXG4gICdCSVQnOiAnLTEyMDAnLFxuICAnQk9UJzogJy0wNDAwJyxcbiAgJ0JSVCc6ICctMDMwMCcsXG4gICdCU1QnOiAnKzA2MDAnLFxuICAnQlRUJzogJyswNjAwJyxcbiAgJ0NBVCc6ICcrMDIwMCcsXG4gICdDQ1QnOiAnKzA2MzAnLFxuICAnQ0RUJzogJy0wNTAwJyxcbiAgJ0NFRFQnOiAnKzAyMDAnLFxuICAnQ0VTVCc6ICcrMDIwMCcsXG4gICdDRVQnOiAnKzAxMDAnLFxuICAnQ0hBRFQnOiAnKzEzNDUnLFxuICAnQ0hBU1QnOiAnKzEyNDUnLFxuICAnQ0hPVCc6ICcrMDgwMCcsXG4gICdDSFNUJzogJysxMDAwJyxcbiAgJ0NIVVQnOiAnKzEwMDAnLFxuICAnQ0lTVCc6ICctMDgwMCcsXG4gICdDSVQnOiAnKzA4MDAnLFxuICAnQ0tUJzogJy0xMDAwJyxcbiAgJ0NMU1QnOiAnLTAzMDAnLFxuICAnQ0xUJzogJy0wNDAwJyxcbiAgJ0NPU1QnOiAnLTA0MDAnLFxuICAnQ09UJzogJy0wNTAwJyxcbiAgJ0NTVCc6ICctMDYwMCcsXG4gICdDVCc6ICcrMDgwMCcsXG4gICdDVlQnOiAnLTAxMDAnLFxuICAnQ1dTVCc6ICcrMDg0NScsXG4gICdDWFQnOiAnKzA3MDAnLFxuICAnREFWVCc6ICcrMDcwMCcsXG4gICdERFVUJzogJysxMDAwJyxcbiAgJ0RGVCc6ICcrMDEwMCcsXG4gICdFQVNTVCc6ICctMDUwMCcsXG4gICdFQVNUJzogJy0wNjAwJyxcbiAgJ0VBVCc6ICcrMDMwMCcsXG4gICdFQ1QnOiAnLTA1MDAnLFxuICAnRURUJzogJy0wNDAwJyxcbiAgJ0VFRFQnOiAnKzAzMDAnLFxuICAnRUVTVCc6ICcrMDMwMCcsXG4gICdFRVQnOiAnKzAyMDAnLFxuICAnRUdTVCc6ICcrMDAwMCcsXG4gICdFR1QnOiAnLTAxMDAnLFxuICAnRUlUJzogJyswOTAwJyxcbiAgJ0VTVCc6ICctMDUwMCcsXG4gICdGRVQnOiAnKzAzMDAnLFxuICAnRkpUJzogJysxMjAwJyxcbiAgJ0ZLU1QnOiAnLTAzMDAnLFxuICAnRktUJzogJy0wNDAwJyxcbiAgJ0ZOVCc6ICctMDIwMCcsXG4gICdHQUxUJzogJy0wNjAwJyxcbiAgJ0dBTVQnOiAnLTA5MDAnLFxuICAnR0VUJzogJyswNDAwJyxcbiAgJ0dGVCc6ICctMDMwMCcsXG4gICdHSUxUJzogJysxMjAwJyxcbiAgJ0dJVCc6ICctMDkwMCcsXG4gICdHTVQnOiAnKzAwMDAnLFxuICAnR1NUJzogJyswNDAwJyxcbiAgJ0dZVCc6ICctMDQwMCcsXG4gICdIQURUJzogJy0wOTAwJyxcbiAgJ0hBRUMnOiAnKzAyMDAnLFxuICAnSEFTVCc6ICctMTAwMCcsXG4gICdIS1QnOiAnKzA4MDAnLFxuICAnSE1UJzogJyswNTAwJyxcbiAgJ0hPVlQnOiAnKzA3MDAnLFxuICAnSFNUJzogJy0xMDAwJyxcbiAgJ0lDVCc6ICcrMDcwMCcsXG4gICdJRFQnOiAnKzAzMDAnLFxuICAnSU9UJzogJyswMzAwJyxcbiAgJ0lSRFQnOiAnKzA0MzAnLFxuICAnSVJLVCc6ICcrMDkwMCcsXG4gICdJUlNUJzogJyswMzMwJyxcbiAgJ0lTVCc6ICcrMDUzMCcsXG4gICdKU1QnOiAnKzA5MDAnLFxuICAnS0dUJzogJyswNjAwJyxcbiAgJ0tPU1QnOiAnKzExMDAnLFxuICAnS1JBVCc6ICcrMDcwMCcsXG4gICdLU1QnOiAnKzA5MDAnLFxuICAnTEhTVCc6ICcrMTAzMCcsXG4gICdMSU5UJzogJysxNDAwJyxcbiAgJ01BR1QnOiAnKzEyMDAnLFxuICAnTUFSVCc6ICctMDkzMCcsXG4gICdNQVdUJzogJyswNTAwJyxcbiAgJ01EVCc6ICctMDYwMCcsXG4gICdNRVQnOiAnKzAxMDAnLFxuICAnTUVTVCc6ICcrMDIwMCcsXG4gICdNSFQnOiAnKzEyMDAnLFxuICAnTUlTVCc6ICcrMTEwMCcsXG4gICdNSVQnOiAnLTA5MzAnLFxuICAnTU1UJzogJyswNjMwJyxcbiAgJ01TSyc6ICcrMDQwMCcsXG4gICdNU1QnOiAnLTA3MDAnLFxuICAnTVVUJzogJyswNDAwJyxcbiAgJ01WVCc6ICcrMDUwMCcsXG4gICdNWVQnOiAnKzA4MDAnLFxuICAnTkNUJzogJysxMTAwJyxcbiAgJ05EVCc6ICctMDIzMCcsXG4gICdORlQnOiAnKzExMzAnLFxuICAnTlBUJzogJyswNTQ1JyxcbiAgJ05TVCc6ICctMDMzMCcsXG4gICdOVCc6ICctMDMzMCcsXG4gICdOVVQnOiAnLTExMDAnLFxuICAnTlpEVCc6ICcrMTMwMCcsXG4gICdOWlNUJzogJysxMjAwJyxcbiAgJ09NU1QnOiAnKzA3MDAnLFxuICAnT1JBVCc6ICcrMDUwMCcsXG4gICdQRFQnOiAnLTA3MDAnLFxuICAnUEVUJzogJy0wNTAwJyxcbiAgJ1BFVFQnOiAnKzEyMDAnLFxuICAnUEdUJzogJysxMDAwJyxcbiAgJ1BIT1QnOiAnKzEzMDAnLFxuICAnUEhUJzogJyswODAwJyxcbiAgJ1BLVCc6ICcrMDUwMCcsXG4gICdQTURUJzogJy0wMjAwJyxcbiAgJ1BNU1QnOiAnLTAzMDAnLFxuICAnUE9OVCc6ICcrMTEwMCcsXG4gICdQU1QnOiAnLTA4MDAnLFxuICAnUFlTVCc6ICctMDMwMCcsXG4gICdQWVQnOiAnLTA0MDAnLFxuICAnUkVUJzogJyswNDAwJyxcbiAgJ1JPVFQnOiAnLTAzMDAnLFxuICAnU0FLVCc6ICcrMTEwMCcsXG4gICdTQU1UJzogJyswNDAwJyxcbiAgJ1NBU1QnOiAnKzAyMDAnLFxuICAnU0JUJzogJysxMTAwJyxcbiAgJ1NDVCc6ICcrMDQwMCcsXG4gICdTR1QnOiAnKzA4MDAnLFxuICAnU0xTVCc6ICcrMDUzMCcsXG4gICdTUlQnOiAnLTAzMDAnLFxuICAnU1NUJzogJyswODAwJyxcbiAgJ1NZT1QnOiAnKzAzMDAnLFxuICAnVEFIVCc6ICctMTAwMCcsXG4gICdUSEEnOiAnKzA3MDAnLFxuICAnVEZUJzogJyswNTAwJyxcbiAgJ1RKVCc6ICcrMDUwMCcsXG4gICdUS1QnOiAnKzEzMDAnLFxuICAnVExUJzogJyswOTAwJyxcbiAgJ1RNVCc6ICcrMDUwMCcsXG4gICdUT1QnOiAnKzEzMDAnLFxuICAnVFZUJzogJysxMjAwJyxcbiAgJ1VDVCc6ICcrMDAwMCcsXG4gICdVTEFUJzogJyswODAwJyxcbiAgJ1VUQyc6ICcrMDAwMCcsXG4gICdVWVNUJzogJy0wMjAwJyxcbiAgJ1VZVCc6ICctMDMwMCcsXG4gICdVWlQnOiAnKzA1MDAnLFxuICAnVkVUJzogJy0wNDMwJyxcbiAgJ1ZMQVQnOiAnKzEwMDAnLFxuICAnVk9MVCc6ICcrMDQwMCcsXG4gICdWT1NUJzogJyswNjAwJyxcbiAgJ1ZVVCc6ICcrMTEwMCcsXG4gICdXQUtUJzogJysxMjAwJyxcbiAgJ1dBU1QnOiAnKzAyMDAnLFxuICAnV0FUJzogJyswMTAwJyxcbiAgJ1dFRFQnOiAnKzAxMDAnLFxuICAnV0VTVCc6ICcrMDEwMCcsXG4gICdXRVQnOiAnKzAwMDAnLFxuICAnV1NUJzogJyswODAwJyxcbiAgJ1lBS1QnOiAnKzEwMDAnLFxuICAnWUVLVCc6ICcrMDYwMCcsXG4gICdaJzogJyswMDAwJ1xufVxuIl19 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emailjs-mime-parser", 3 | "version": "2.0.7", 4 | "homepage": "https://github.com/emailjs/emailjs-mime-parser", 5 | "description": "Parse a mime tree, no magic included.", 6 | "author": "Andris Reinman ", 7 | "keywords": [ 8 | "mime" 9 | ], 10 | "license": "MIT", 11 | "scripts": { 12 | "build": "./scripts/build.sh", 13 | "lint": "$(npm bin)/standard", 14 | "preversion": "npm run build", 15 | "test": "npm run lint && npm run unit", 16 | "unit": "$(npm bin)/mocha './src/*-unit.js' --reporter spec --require babel-register testutils.js", 17 | "test-watch": "$(npm bin)/mocha './src/*-unit.js' --reporter spec --require babel-register testutils.js --watch" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/emailjs/emailjs-mime-parser.git" 22 | }, 23 | "main": "dist/mimeparser", 24 | "dependencies": { 25 | "emailjs-addressparser": "^2.0.2", 26 | "emailjs-mime-codec": "^2.0.8", 27 | "ramda": "^0.26.1" 28 | }, 29 | "devDependencies": { 30 | "babel-cli": "^6.26.0", 31 | "babel-preset-es2015": "^6.24.1", 32 | "babel-register": "^6.26.0", 33 | "chai": "^4.2.0", 34 | "mocha": "^6.1.4", 35 | "nodemon": "^1.19.1", 36 | "pre-commit": "^1.2.2", 37 | "sinon": "^7.3.2", 38 | "standard": "^12.0.1", 39 | "text-encoding": "^0.7.0" 40 | }, 41 | "standard": { 42 | "globals": [ 43 | "sinon", 44 | "describe", 45 | "it", 46 | "before", 47 | "beforeEach", 48 | "afterEach", 49 | "after", 50 | "expect" 51 | ], 52 | "ignore": [ 53 | "dist" 54 | ] 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf $PWD/dist 4 | babel src --out-dir dist --ignore '**/*-unit.js' --source-maps inline 5 | git reset 6 | git add $PWD/dist 7 | git commit -m 'Updating dist files' -n 8 | -------------------------------------------------------------------------------- /src/mimeparser-unit.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-expressions */ 2 | 3 | import parse, { MimeNode, NodeCounter } from './mimeparser' 4 | import { TextDecoder } from 'text-encoding' 5 | 6 | describe('Header parsing', () => { 7 | it('should succeed', function () { 8 | var fixture = 'From: Sender Name \r\n' + 9 | 'Subject: Hello world!\r\n' + 10 | 'Date: Fri, 4 Oct 2013 07:17:32 +0000\r\n' + 11 | 'Message-Id: \r\n' + 12 | 'Content-Type: multipart/signed; protocol="TYPE/STYPE"; micalg="MICALG"; boundary="Signed Boundary"\r\n' + 13 | 'Content-Transfer-Encoding: quoted-printable\r\n' + 14 | '\r\n' 15 | 16 | const root = parse(fixture) 17 | expect(root.headers.from).to.deep.equal([{ 18 | value: [{ 19 | address: 'sender.name@example.com', 20 | name: 'Sender Name' 21 | }], 22 | initial: 'Sender Name ' 23 | }]) 24 | 25 | expect(root.headers.subject).to.deep.equal([{ 26 | value: 'Hello world!', 27 | initial: 'Hello world!' 28 | }]) 29 | 30 | expect(root.headers['content-transfer-encoding']).to.deep.equal([{ 31 | value: 'quoted-printable', 32 | initial: 'quoted-printable', 33 | params: {} 34 | }]) 35 | expect(root.contentTransferEncoding).to.deep.equal({ 36 | value: 'quoted-printable', 37 | initial: 'quoted-printable', 38 | params: {} 39 | }) 40 | 41 | expect(root.headers['content-type']).to.deep.equal([{ 42 | value: 'multipart/signed', 43 | params: { 44 | boundary: 'Signed Boundary', 45 | micalg: 'MICALG', 46 | protocol: 'TYPE/STYPE' 47 | }, 48 | type: 'multipart', 49 | initial: 'multipart/signed; protocol="TYPE/STYPE"; micalg="MICALG"; boundary="Signed Boundary"' 50 | }]) 51 | }) 52 | 53 | it('should parse encoded headers', function () { 54 | var fixture = 'Subject: =?iso-8859-1?Q?Avaldu?= =?iso-8859-1?Q?s_lepingu_?=\r\n' + 55 | ' =?iso-8859-1?Q?l=F5petamise?= =?iso-8859-1?Q?ks?=\r\n' + 56 | 'Content-Disposition: attachment;\r\n' + 57 | ' filename*0*=UTF-8\'\'%C3%95%C3%84;\r\n' + 58 | ' filename*1*=%C3%96%C3%9C\r\n' + 59 | 'From: =?gb2312?B?086yyZjl?= user@ldkf.com.tw\r\n' + 60 | 'To: =?UTF-8?Q?=C3=95=C3=84=C3=96=C3=9C?=:=?gb2312?B?086yyZjl?= user@ldkf.com.tw;\r\n' + 61 | 'Content-Disposition: attachment; filename="=?UTF-8?Q?=C3=95=C3=84=C3=96=C3=9C?="\r\n' + 62 | '\r\n' + 63 | 'abc' 64 | 65 | const root = parse(fixture) 66 | 67 | expect(root.headers['subject']).to.deep.equal([{ 68 | value: 'Avaldus lepingu lõpetamiseks', 69 | initial: '=?iso-8859-1?Q?Avaldu?= =?iso-8859-1?Q?s_lepingu_?= =?iso-8859-1?Q?l=F5petamise?= =?iso-8859-1?Q?ks?=' 70 | }]) 71 | expect(root.headers['content-disposition']).to.deep.equal([{ 72 | value: 'attachment', 73 | params: { 74 | filename: 'ÕÄÖÜ' 75 | }, 76 | initial: 'attachment; filename*0*=UTF-8\'\'%C3%95%C3%84; filename*1*=%C3%96%C3%9C' 77 | }, { 78 | value: 'attachment', 79 | params: { 80 | filename: 'ÕÄÖÜ' 81 | }, 82 | initial: 'attachment; filename="=?UTF-8?Q?=C3=95=C3=84=C3=96=C3=9C?="' 83 | }]) 84 | expect(root.headers['from']).to.deep.equal([{ 85 | value: [{ 86 | address: 'user@ldkf.com.tw', 87 | name: '游采樺' 88 | }], 89 | initial: '=?gb2312?B?086yyZjl?= user@ldkf.com.tw' 90 | }]) 91 | expect(root.headers['to']).to.deep.equal([{ 92 | value: [{ 93 | name: 'ÕÄÖÜ', 94 | group: [{ 95 | address: 'user@ldkf.com.tw', 96 | name: '游采樺' 97 | }] 98 | }], 99 | initial: '=?UTF-8?Q?=C3=95=C3=84=C3=96=C3=9C?=:=?gb2312?B?086yyZjl?= user@ldkf.com.tw;' 100 | }]) 101 | }) 102 | 103 | it('should use latin1 as the default for headers', function () { 104 | var fixture = 'a: \xD5\xC4\xD6\xDC\r\n' + 105 | 'Content-Type: text/plain\r\n' + 106 | 'b: \xD5\xC4\xD6\xDC\r\n' + 107 | '\r\n' + 108 | '' 109 | const root = parse(fixture) 110 | expect(root.headers.a[0].value).to.equal('ÕÄÖÜ') 111 | expect(root.headers.b[0].value).to.equal('ÕÄÖÜ') 112 | }) 113 | 114 | it('should detect 8bit header encoding', function () { 115 | var fixture = 'a: \xC3\x95\xC3\x84\xC3\x96\xC3\x9C\r\n' + 116 | 'Content-Type: text/plain; charset=utf-8\r\n' + 117 | 'b: \xC3\x95\xC3\x84\xC3\x96\xC3\x9C\r\n' + 118 | '\r\n' 119 | const root = parse(fixture) 120 | expect(root.headers.a[0].value).to.equal('ÕÄÖÜ') 121 | expect(root.headers.b[0].value).to.equal('ÕÄÖÜ') 122 | }) 123 | }) 124 | 125 | describe('Body parsing', () => { 126 | it('should decode unencoded 7bit input', function () { 127 | var fixture = 'Content-Type: text/plain\r\n' + 128 | '\r\n' + 129 | 'xxxx\r\n' + 130 | 'yyyy' 131 | const root = parse(fixture) 132 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('xxxx\r\nyyyy') 133 | }) 134 | 135 | it('should decode utf-8 base64', function () { 136 | var fixture = 'Content-Type: text/plain; charset="utf-8"\r\n' + 137 | 'Content-Transfer-Encoding: base64\r\n' + 138 | '\r\n' + 139 | '4pSB4pSB4pSB4pSB4pSB4pSB4pSB4pSB4pSBCuacrOODoeODvOODq+OBr+OAgeODnuOCpOODiuOD\r\n' 140 | const root = parse(fixture) 141 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('━━━━━━━━━\n本メールは、マイナ�') 142 | }) 143 | 144 | it('should parse UTF-8 encoded body with Base64 transfer encoding', function () { 145 | var fixture = 'Mime-Version: 1.0\r\n' + 146 | 'Content-Type: text/plain; charset=UTF-8\r\n' + 147 | 'Content-Transfer-Encoding: base64\r\n' + 148 | '\r\n' + 149 | 'CuKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKU\r\n' + 150 | 'geKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKUgeKU\r\n' + 151 | 'geKUgeKUgQoK44CA44CA44CA44CA44CA44CA44CAWWFob28h44Kr44O844OJ\r\n' + 152 | '44CA44GK44GZ44GZ44KB5oOF5aCx44Oh44O844Or\r\n' 153 | const root = parse(fixture) 154 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal( 155 | '\n' + 156 | '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + 157 | '\n' + 158 | '       Yahoo!カード おすすめ情報メール') 159 | }) 160 | 161 | it('should decode latin-1 quoted-printable', function () { 162 | var fixture = 'Content-Type: text/plain; charset="latin_1"\r\n' + 163 | 'Content-Transfer-Encoding: quoted-printable\r\n' + 164 | '\r\n' + 165 | 'l=F5petam' 166 | var expectedText = 'lõpetam' 167 | const root = parse(fixture) 168 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal(expectedText) 169 | }) 170 | 171 | it('should ignore charset for plaintext attachment', function () { 172 | var fixture = 'Content-Type: text/plain; charset="latin_1"\r\n' + 173 | 'Content-Disposition: attachment\r\n' + 174 | 'Content-Transfer-Encoding: quoted-printable\r\n' + 175 | '\r\n' + 176 | 'l=F5petam' 177 | var expectedText = 'lõpetam' 178 | const root = parse(fixture) 179 | expect(new TextDecoder('iso-8859-1').decode(root.content)).to.equal(expectedText) 180 | }) 181 | 182 | // TODO Add test for multipart message 183 | // TODO Add test for RFC-822 184 | 185 | describe('HTML parsing', () => { 186 | it('should detect charset from html meta and convert html text to utf-8', function () { 187 | var fixture = 'Content-Type: text/plain;\r\n' + 188 | 'Content-Transfer-Encoding: quoted-printable\r\n' + 189 | '\r\n' + 190 | '=3Cmeta=20charset=3D=22latin_1=22/=3E=D5=C4=D6=DC' 191 | var expectedText = 'ÕÄÖÜ' 192 | const root = parse(fixture) 193 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal(expectedText) 194 | }) 195 | }) 196 | 197 | describe('Flowed text formatting', () => { 198 | it('should parse format=flowed text', function () { 199 | var fixture = 'Content-Type: text/plain; format=flowed\r\n\r\nFirst line \r\ncontinued \r\nand so on\n-- \nSignature\ntere\n From\n Hello\n > abc\nabc\n' 200 | 201 | const root = parse(fixture) 202 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('First line continued and so on\n-- \nSignature\ntere\nFrom\n Hello\n> abc\nabc\n') 203 | }) 204 | 205 | it('should not corrupt format=flowed text that is not flowed', function () { 206 | var fixture = 'Content-Type: text/plain; format=flowed\r\n\r\nFirst line.\r\nSecond line.\r\n' 207 | 208 | const root = parse(fixture) 209 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('First line.\r\nSecond line.\r\n') 210 | }) 211 | 212 | it('should parse format=fixed text', function () { 213 | var fixture = 'Content-Type: text/plain; format=fixed\r\n\r\nFirst line \r\ncontinued \r\nand so on' 214 | 215 | const root = parse(fixture) 216 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('First line \r\ncontinued \r\nand so on') 217 | }) 218 | 219 | it('should parse delsp=yes text', function () { 220 | var fixture = 'Content-Type: text/plain; format=flowed; delsp=yes\r\n\r\nFirst line \r\ncontinued \r\nand so on' 221 | 222 | const root = parse(fixture) 223 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal('First linecontinuedand so on') 224 | }) 225 | }) 226 | }) 227 | 228 | describe('Message parsing', function () { 229 | it('should succeed', function () { 230 | var fixture = 'Content-Type: text/plain; charset="utf-8"\r\n' + 231 | 'Content-Transfer-Encoding: quoted-printable\r\n' + 232 | '\r\n' + 233 | '\r\n' + 234 | 'Hi,\r\n' + 235 | '\r\n' + 236 | 'this is a private conversation. To read my encrypted message below, simply =\r\n' + 237 | 'open it in Whiteout Mail.\r\n' + 238 | 'Open Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamhol=\r\n' + 239 | 'jigjoghcfcekhkonijg\r\n' + 240 | '\r\n' 241 | var expectedText = '\r\nHi,\r\n\r\nthis is a private conversation. To read my encrypted message below, simply open it in Whiteout Mail.\r\nOpen Whiteout Mail: https://chrome.google.com/webstore/detail/jjgghafhamholjigjoghcfcekhkonijg\r\n\r\n' 242 | 243 | const root = parse(fixture) 244 | expect(new TextDecoder('utf-8').decode(root.content)).to.equal(expectedText) 245 | }) 246 | 247 | it('should decode S/MIME', () => { 248 | const testHeader = 'Content-Type: application/pkcs7-mime; name=smime.p7m; smime-type=enveloped-data; charset=binary\r\n' + 249 | 'Content-Description: Enveloped Data\r\n' + 250 | 'Content-Disposition: attachment; filename=smime.p7m\r\n' + 251 | 'Content-Transfer-Encoding: base64\r\n' + 252 | 'From: sender@example.com\r\n' + 253 | 'To: recipient@example.com\r\n' + 254 | 'Subject: Example S/MIME encrypted message\r\n' + 255 | 'Date: Sun, 25 Feb 2018 09:28:14 +0000\r\n' + 256 | 'Message-Id: <1519550894482-b208bdc8-7f8e90aa-4af6b4fa@example.com>\r\n' + 257 | 'MIME-Version: 1.0\r\n' + 258 | '\r\n' 259 | 260 | const testMessage = 'MIIB3AYJKoZIhvcNAQcDoIIBzTCCAckCAQIxggFuMIIBagIBADAjMB4xHDAJBgNVBAYTAlJVMA8G\r\n' + 261 | 'A1UEAx4IAFQAZQBzAHQCAQEwPAYJKoZIhvcNAQEHMC+gDzANBglghkgBZQMEAgMFAKEcMBoGCSqG\r\n' + 262 | 'SIb3DQEBCDANBglghkgBZQMEAgMFAASCAQBDyepahKyM+hceeF7J+pyiSVYLElKyFKff9flMs1VX\r\n' + 263 | 'ZaBQRcEYpIqw9agD4u+aHlIOJ6AtdCbxaV0M8q6gjM4E5lUFUOqG/QIycdG2asZ0lza/DL8SdxfA\r\n' + 264 | '3WE9Ij5IEqFbtnykbfORK+5XWT0nYs/OMN0NKeCwXjElNsezX9IAIgxHgwcVYW+szXpRlarjriAC\r\n' + 265 | 'TDG/M+Xl5YtyAhmHWFncBSfWM8e2q+AKh3eCal1lH4eXtGICc4rad4f6845YJwXL8DYYS+GdLVAY\r\n' + 266 | 'EXKuHr0N7g4aHTs9B8EQqHmYdaHWTi3h0ZPkvAE+wwfm9xjvL2z2HrfpYyMTvALrefvSt7sGMIAG\r\n' + 267 | 'CSqGSIb3DQEHATAdBglghkgBZQMEAQIEEKt6VqFcNz/VYFwu85DTOqGggAQgIHc45LBiYIQqhxNw\r\n' + 268 | 'hlRk4BxMiyiQRdLcVdCwwkKyX2sAAAAA\r\n' 269 | 270 | const expectedText = (Buffer.from(testMessage, 'base64')).toString('hex').toUpperCase() 271 | const root = parse(testHeader + testMessage) 272 | expect((Buffer.from(root.content.buffer)).toString('hex').toUpperCase()).to.deep.equal(expectedText) 273 | }) 274 | 275 | it('should be resilient to memory exhaustion attack', () => { 276 | let numberOfMimeNodes = 9999 277 | const attackPayload = [] 278 | while (numberOfMimeNodes--) { 279 | attackPayload.push('--0\r\n\r\n\r\n') 280 | } 281 | const fixture = 'Content-Type: multipart/mixed; boundary="0"\r\n\r\n' + attackPayload.join('') 282 | expect(() => parse(fixture)).to.throw('Maximum number of MIME nodes exceeded!') 283 | }) 284 | }) 285 | 286 | describe('Bodystructure', () => { 287 | it('should emit structure without values of nodes', function () { 288 | var fixture = 289 | 'MIME-Version: 1.0\n' + 290 | 'Content-Type: multipart/mixed;\n' + 291 | ' boundary="------------304E429112E7D6AC36F087A8"\n' + 292 | '\n' + 293 | 'This is a multi-part message in MIME format.\n' + 294 | '--------------304E429112E7D6AC36F087A8\n' + 295 | 'Content-Type: text/html; charset=utf-8\n' + 296 | 'Content-Transfer-Encoding: 7bit\n' + 297 | '\n' + 298 | '\n' + 299 | '--------------304E429112E7D6AC36F087A8\n' + 300 | 'Content-Type: text/plain; charset=UTF-8; x-mac-type="0"; x-mac-creator="0";\n' + 301 | ' name="hello.mime"\n' + 302 | 'Content-Transfer-Encoding: base64\n' + 303 | 'Content-Disposition: attachment;\n' + 304 | ' filename="hello.mime"\n' + 305 | '\n' + 306 | 'SGkgbW9tIQ==\n' + 307 | '--------------304E429112E7D6AC36F087A8--\n' 308 | 309 | var expectedBodystructure = 310 | 'MIME-Version: 1.0\n' + 311 | 'Content-Type: multipart/mixed;\n' + 312 | ' boundary="------------304E429112E7D6AC36F087A8"\n' + 313 | '\n' + 314 | '--------------304E429112E7D6AC36F087A8\n' + 315 | 'Content-Type: text/html; charset=utf-8\n' + 316 | 'Content-Transfer-Encoding: 7bit\n' + 317 | '\n' + 318 | '--------------304E429112E7D6AC36F087A8\n' + 319 | 'Content-Type: text/plain; charset=UTF-8; x-mac-type="0"; x-mac-creator="0";\n' + 320 | ' name="hello.mime"\n' + 321 | 'Content-Transfer-Encoding: base64\n' + 322 | 'Content-Disposition: attachment;\n' + 323 | ' filename="hello.mime"\n' + 324 | '\n' + 325 | '--------------304E429112E7D6AC36F087A8--\n' 326 | 327 | const root = parse(fixture) 328 | expect(root.childNodes).to.not.be.empty 329 | expect(root.bodystructure).to.equal(expectedBodystructure) 330 | }) 331 | }) 332 | 333 | describe('Date parsing', () => { 334 | it('should parse date header european tz', function () { 335 | const root = parse('Date: Thu, 15 May 2014 13:53:30 EEST\r\n\r\n') 336 | expect(root.headers.date[0].value).to.equal('Thu, 15 May 2014 10:53:30 +0000') 337 | }) 338 | 339 | it('should parse Date object', function () { 340 | const root = parse('Date: Thu, 15 May 2014 11:53:30 +0100\r\n\r\n') 341 | expect(root.headers.date[0].value).to.equal('Thu, 15 May 2014 10:53:30 +0000') 342 | }) 343 | 344 | it('should parse Date object with tz abbr', function () { 345 | const root = parse('Date: Thu, 15 May 2014 10:53:30 UTC\r\n\r\n') 346 | expect(root.headers.date[0].value).to.equal('Thu, 15 May 2014 10:53:30 +0000') 347 | }) 348 | 349 | it('should return original on unexpected input', function () { 350 | const root = parse('Date: Thu, 15 May 2014 13:53:30 YYY\r\n\r\n') 351 | expect(root.headers.date[0].value).to.equal('Thu, 15 May 2014 13:53:30 +0000') 352 | }) 353 | }) 354 | 355 | describe('MimeNode', function () { 356 | let node 357 | 358 | beforeEach(() => { 359 | node = new MimeNode() 360 | }) 361 | 362 | describe('#fetchContentType', function () { 363 | it('should fetch special properties from content-type header', function () { 364 | node.headers['content-type'] = [{ 365 | value: 'multipart/mixed', 366 | params: { 367 | charset: 'utf-8', 368 | boundary: 'zzzz' 369 | } 370 | }] 371 | 372 | node.fetchContentType() 373 | 374 | expect(node.contentType).to.deep.equal({ 375 | value: 'multipart/mixed', 376 | type: 'multipart', 377 | params: { 378 | charset: 'utf-8', 379 | boundary: 'zzzz' 380 | } 381 | }) 382 | expect(node.charset).to.equal('utf-8') 383 | expect(node._isMultipart).to.equal('mixed') 384 | expect(node._multipartBoundary).to.equal('zzzz') 385 | }) 386 | 387 | it('should set charset to binary for attachment when there is no charset', function () { 388 | node.headers['content-type'] = [{ 389 | value: 'application/pdf' 390 | }] 391 | 392 | node.headers['content-disposition'] = [{ 393 | value: 'attachment' 394 | }] 395 | 396 | node.fetchContentType() 397 | 398 | expect(node.contentType).to.deep.equal({ 399 | value: 'application/pdf', 400 | type: 'application' 401 | }) 402 | expect(node.charset).to.equal('binary') 403 | }) 404 | 405 | it('should set charset to binary for inline attachment when there is no charset', function () { 406 | node.headers['content-type'] = [{ 407 | value: 'image/png' 408 | }] 409 | 410 | node.headers['content-disposition'] = [{ 411 | value: 'inline' 412 | }] 413 | 414 | node.fetchContentType() 415 | 416 | expect(node.contentType).to.deep.equal({ 417 | value: 'image/png', 418 | type: 'image' 419 | }) 420 | expect(node.charset).to.equal('binary') 421 | }) 422 | 423 | it('should not set charset to binary for inline attachment when there is a charset', function () { 424 | node.headers['content-type'] = [{ 425 | value: 'image/png', 426 | params: { 427 | charset: 'US-ASCII' 428 | } 429 | }] 430 | 431 | node.headers['content-disposition'] = [{ 432 | value: 'inline' 433 | }] 434 | 435 | node.fetchContentType() 436 | 437 | expect(node.contentType).to.deep.equal({ 438 | value: 'image/png', 439 | type: 'image', 440 | params: { 441 | charset: 'US-ASCII' 442 | } 443 | }) 444 | expect(node.charset).to.equal('US-ASCII') 445 | }) 446 | 447 | it('should not set charset to binary for text/* attachment when there is no charset', function () { 448 | node.headers['content-type'] = [{ 449 | value: 'text/plain' 450 | }] 451 | 452 | node.headers['content-disposition'] = [{ 453 | value: 'attachment' 454 | }] 455 | 456 | node.fetchContentType() 457 | 458 | expect(node.contentType).to.deep.equal({ 459 | value: 'text/plain', 460 | type: 'text' 461 | }) 462 | expect(node.charset).to.be.undefined 463 | }) 464 | 465 | it('should not set charset to binary for text/* attachment when there is a charset', function () { 466 | node.headers['content-type'] = [{ 467 | value: 'text/plain', 468 | params: { 469 | charset: 'US-ASCII' 470 | } 471 | }] 472 | 473 | node.headers['content-disposition'] = [{ 474 | value: 'attachment' 475 | }] 476 | 477 | node.fetchContentType() 478 | 479 | expect(node.contentType).to.deep.equal({ 480 | value: 'text/plain', 481 | type: 'text', 482 | params: { 483 | charset: 'US-ASCII' 484 | } 485 | }) 486 | expect(node.charset).to.equal('US-ASCII') 487 | }) 488 | }) 489 | }) 490 | 491 | describe('NodeCounter', () => { 492 | it('should throw at the 1000th invocation', () => { 493 | let invocations = 999 494 | const nodeCounter = new NodeCounter() 495 | while (invocations--) { 496 | expect(() => nodeCounter.bump()).to.not.throw() 497 | } 498 | expect(() => nodeCounter.bump()).to.throw() 499 | }) 500 | }) 501 | -------------------------------------------------------------------------------- /src/mimeparser.js: -------------------------------------------------------------------------------- 1 | import { pathOr } from 'ramda' 2 | import timezone from './timezones' 3 | import { decode, base64Decode, convert, parseHeaderValue, mimeWordsDecode } from 'emailjs-mime-codec' 4 | import { TextEncoder } from 'text-encoding' 5 | import parseAddress from 'emailjs-addressparser' 6 | 7 | /* 8 | * Counts MIME nodes to prevent memory exhaustion attacks (CWE-400) 9 | * see: https://snyk.io/vuln/npm:emailjs-mime-parser:20180625 10 | */ 11 | const MAXIMUM_NUMBER_OF_MIME_NODES = 999 12 | export class NodeCounter { 13 | constructor () { 14 | this.count = 0 15 | } 16 | bump () { 17 | if (++this.count > MAXIMUM_NUMBER_OF_MIME_NODES) { 18 | throw new Error('Maximum number of MIME nodes exceeded!') 19 | } 20 | } 21 | } 22 | 23 | function forEachLine (str, callback) { 24 | let line = '' 25 | let terminator = '' 26 | for (var i = 0; i < str.length; i += 1) { 27 | const char = str[i] 28 | if (char === '\r' || char === '\n') { 29 | const nextChar = str[i + 1] 30 | terminator += char 31 | // Detect Windows and Macintosh line terminators. 32 | if ((terminator + nextChar) === '\r\n' || (terminator + nextChar) === '\n\r') { 33 | callback(line, terminator + nextChar) 34 | line = '' 35 | terminator = '' 36 | i += 1 37 | // Detect single-character terminators, like Linux or other system. 38 | } else if (terminator === '\n' || terminator === '\r') { 39 | callback(line, terminator) 40 | line = '' 41 | terminator = '' 42 | } 43 | } else { 44 | line += char 45 | } 46 | } 47 | // Flush the line and terminator values if necessary; handle edge cases where MIME is generated without last line terminator. 48 | if (line !== '' || terminator !== '') { 49 | callback(line, terminator) 50 | } 51 | } 52 | 53 | export default function parse (chunk) { 54 | const root = new MimeNode(new NodeCounter()) 55 | const str = typeof chunk === 'string' ? chunk : String.fromCharCode.apply(null, chunk) 56 | forEachLine(str, function (line, terminator) { 57 | root.writeLine(line, terminator) 58 | }) 59 | root.finalize() 60 | return root 61 | } 62 | 63 | export class MimeNode { 64 | constructor (nodeCounter = new NodeCounter()) { 65 | this.nodeCounter = nodeCounter 66 | this.nodeCounter.bump() 67 | 68 | this.header = [] // An array of unfolded header lines 69 | this.headers = {} // An object that holds header key=value pairs 70 | this.bodystructure = '' 71 | this.childNodes = [] // If this is a multipart or message/rfc822 mime part, the value will be converted to array and hold all child nodes for this node 72 | this.raw = '' // Stores the raw content of this node 73 | 74 | this._state = 'HEADER' // Current state, always starts out with HEADER 75 | this._bodyBuffer = '' // Body buffer 76 | this._lineCount = 0 // Line counter bor the body part 77 | this._currentChild = false // Active child node (if available) 78 | this._lineRemainder = '' // Remainder string when dealing with base64 and qp values 79 | this._isMultipart = false // Indicates if this is a multipart node 80 | this._multipartBoundary = false // Stores boundary value for current multipart node 81 | this._isRfc822 = false // Indicates if this is a message/rfc822 node 82 | } 83 | 84 | writeLine (line, terminator) { 85 | this.raw += line + (terminator || '\n') 86 | 87 | if (this._state === 'HEADER') { 88 | this._processHeaderLine(line) 89 | } else if (this._state === 'BODY') { 90 | this._processBodyLine(line, terminator) 91 | } 92 | } 93 | 94 | finalize () { 95 | if (this._isRfc822) { 96 | this._currentChild.finalize() 97 | } else { 98 | this._emitBody() 99 | } 100 | 101 | this.bodystructure = this.childNodes 102 | .reduce((agg, child) => agg + '--' + this._multipartBoundary + '\n' + child.bodystructure, this.header.join('\n') + '\n\n') + 103 | (this._multipartBoundary ? '--' + this._multipartBoundary + '--\n' : '') 104 | } 105 | 106 | _decodeBodyBuffer () { 107 | switch (this.contentTransferEncoding.value) { 108 | case 'base64': 109 | this._bodyBuffer = base64Decode(this._bodyBuffer, this.charset) 110 | break 111 | case 'quoted-printable': { 112 | this._bodyBuffer = this._bodyBuffer 113 | .replace(/=(\r?\n|$)/g, '') 114 | .replace(/=([a-f0-9]{2})/ig, (m, code) => String.fromCharCode(parseInt(code, 16))) 115 | break 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * Processes a line in the HEADER state. It the line is empty, change state to BODY 122 | * 123 | * @param {String} line Entire input line as 'binary' string 124 | */ 125 | _processHeaderLine (line) { 126 | if (!line) { 127 | this._parseHeaders() 128 | this.bodystructure += this.header.join('\n') + '\n\n' 129 | this._state = 'BODY' 130 | return 131 | } 132 | 133 | if (line.match(/^\s/) && this.header.length) { 134 | this.header[this.header.length - 1] += '\n' + line 135 | } else { 136 | this.header.push(line) 137 | } 138 | } 139 | 140 | /** 141 | * Joins folded header lines and calls Content-Type and Transfer-Encoding processors 142 | */ 143 | _parseHeaders () { 144 | for (let hasBinary = false, i = 0, len = this.header.length; i < len; i++) { 145 | let value = this.header[i].split(':') 146 | const key = (value.shift() || '').trim().toLowerCase() 147 | value = (value.join(':') || '').replace(/\n/g, '').trim() 148 | 149 | if (value.match(/[\u0080-\uFFFF]/)) { 150 | if (!this.charset) { 151 | hasBinary = true 152 | } 153 | // use default charset at first and if the actual charset is resolved, the conversion is re-run 154 | value = decode(convert(str2arr(value), this.charset || 'iso-8859-1')) 155 | } 156 | 157 | this.headers[key] = (this.headers[key] || []).concat([this._parseHeaderValue(key, value)]) 158 | 159 | if (!this.charset && key === 'content-type') { 160 | this.charset = this.headers[key][this.headers[key].length - 1].params.charset 161 | } 162 | 163 | if (hasBinary && this.charset) { 164 | // reset values and start over once charset has been resolved and 8bit content has been found 165 | hasBinary = false 166 | this.headers = {} 167 | i = -1 // next iteration has i == 0 168 | } 169 | } 170 | 171 | this.fetchContentType() 172 | this._processContentTransferEncoding() 173 | } 174 | 175 | /** 176 | * Parses single header value 177 | * @param {String} key Header key 178 | * @param {String} value Value for the key 179 | * @return {Object} parsed header 180 | */ 181 | _parseHeaderValue (key, value) { 182 | let parsedValue 183 | let isAddress = false 184 | 185 | switch (key) { 186 | case 'content-type': 187 | case 'content-transfer-encoding': 188 | case 'content-disposition': 189 | case 'dkim-signature': 190 | parsedValue = parseHeaderValue(value) 191 | break 192 | case 'from': 193 | case 'sender': 194 | case 'to': 195 | case 'reply-to': 196 | case 'cc': 197 | case 'bcc': 198 | case 'abuse-reports-to': 199 | case 'errors-to': 200 | case 'return-path': 201 | case 'delivered-to': 202 | isAddress = true 203 | parsedValue = { 204 | value: [].concat(parseAddress(value) || []) 205 | } 206 | break 207 | case 'date': 208 | parsedValue = { 209 | value: this._parseDate(value) 210 | } 211 | break 212 | default: 213 | parsedValue = { 214 | value: value 215 | } 216 | } 217 | parsedValue.initial = value 218 | 219 | this._decodeHeaderCharset(parsedValue, { isAddress }) 220 | 221 | return parsedValue 222 | } 223 | 224 | /** 225 | * Checks if a date string can be parsed. Falls back replacing timezone 226 | * abbrevations with timezone values. Bogus timezones default to UTC. 227 | * 228 | * @param {String} str Date header 229 | * @returns {String} UTC date string if parsing succeeded, otherwise returns input value 230 | */ 231 | _parseDate (str = '') { 232 | const date = new Date(str.trim().replace(/\b[a-z]+$/i, tz => timezone[tz.toUpperCase()] || '+0000')) 233 | return (date.toString() !== 'Invalid Date') ? date.toUTCString().replace(/GMT/, '+0000') : str 234 | } 235 | 236 | _decodeHeaderCharset (parsed, { isAddress } = {}) { 237 | // decode default value 238 | if (typeof parsed.value === 'string') { 239 | parsed.value = mimeWordsDecode(parsed.value) 240 | } 241 | 242 | // decode possible params 243 | Object.keys(parsed.params || {}).forEach(function (key) { 244 | if (typeof parsed.params[key] === 'string') { 245 | parsed.params[key] = mimeWordsDecode(parsed.params[key]) 246 | } 247 | }) 248 | 249 | // decode addresses 250 | if (isAddress && Array.isArray(parsed.value)) { 251 | parsed.value.forEach(addr => { 252 | if (addr.name) { 253 | addr.name = mimeWordsDecode(addr.name) 254 | if (Array.isArray(addr.group)) { 255 | this._decodeHeaderCharset({ value: addr.group }, { isAddress: true }) 256 | } 257 | } 258 | }) 259 | } 260 | 261 | return parsed 262 | } 263 | 264 | /** 265 | * Parses Content-Type value and selects following actions. 266 | */ 267 | fetchContentType () { 268 | const defaultValue = parseHeaderValue('text/plain') 269 | this.contentType = pathOr(defaultValue, ['headers', 'content-type', '0'])(this) 270 | this.contentType.value = (this.contentType.value || '').toLowerCase().trim() 271 | this.contentType.type = (this.contentType.value.split('/').shift() || 'text') 272 | 273 | if (this.contentType.params && this.contentType.params.charset && !this.charset) { 274 | this.charset = this.contentType.params.charset 275 | } 276 | 277 | if (this.contentType.type === 'multipart' && this.contentType.params.boundary) { 278 | this.childNodes = [] 279 | this._isMultipart = (this.contentType.value.split('/').pop() || 'mixed') 280 | this._multipartBoundary = this.contentType.params.boundary 281 | } 282 | 283 | /** 284 | * For attachment (inline/regular) if charset is not defined and attachment is non-text/*, 285 | * then default charset to binary. 286 | * Refer to issue: https://github.com/emailjs/emailjs-mime-parser/issues/18 287 | */ 288 | const defaultContentDispositionValue = parseHeaderValue('') 289 | const contentDisposition = pathOr(defaultContentDispositionValue, ['headers', 'content-disposition', '0'])(this) 290 | const isAttachment = (contentDisposition.value || '').toLowerCase().trim() === 'attachment' 291 | const isInlineAttachment = (contentDisposition.value || '').toLowerCase().trim() === 'inline' 292 | if ((isAttachment || isInlineAttachment) && this.contentType.type !== 'text' && !this.charset) { 293 | this.charset = 'binary' 294 | } 295 | 296 | if (this.contentType.value === 'message/rfc822' && !isAttachment) { 297 | /** 298 | * Parse message/rfc822 only if the mime part is not marked with content-disposition: attachment, 299 | * otherwise treat it like a regular attachment 300 | */ 301 | this._currentChild = new MimeNode(this.nodeCounter) 302 | this.childNodes = [this._currentChild] 303 | this._isRfc822 = true 304 | } 305 | } 306 | 307 | /** 308 | * Parses Content-Transfer-Encoding value to see if the body needs to be converted 309 | * before it can be emitted 310 | */ 311 | _processContentTransferEncoding () { 312 | const defaultValue = parseHeaderValue('7bit') 313 | this.contentTransferEncoding = pathOr(defaultValue, ['headers', 'content-transfer-encoding', '0'])(this) 314 | this.contentTransferEncoding.value = pathOr('', ['contentTransferEncoding', 'value'])(this).toLowerCase().trim() 315 | } 316 | 317 | /** 318 | * Processes a line in the BODY state. If this is a multipart or rfc822 node, 319 | * passes line value to child nodes. 320 | * 321 | * @param {String} line Entire input line as 'binary' string 322 | * @param {String} terminator The line terminator detected by parser 323 | */ 324 | _processBodyLine (line, terminator) { 325 | if (this._isMultipart) { 326 | if (line === '--' + this._multipartBoundary) { 327 | this.bodystructure += line + '\n' 328 | if (this._currentChild) { 329 | this._currentChild.finalize() 330 | } 331 | this._currentChild = new MimeNode(this.nodeCounter) 332 | this.childNodes.push(this._currentChild) 333 | } else if (line === '--' + this._multipartBoundary + '--') { 334 | this.bodystructure += line + '\n' 335 | if (this._currentChild) { 336 | this._currentChild.finalize() 337 | } 338 | this._currentChild = false 339 | } else if (this._currentChild) { 340 | this._currentChild.writeLine(line, terminator) 341 | } else { 342 | // Ignore multipart preamble 343 | } 344 | } else if (this._isRfc822) { 345 | this._currentChild.writeLine(line, terminator) 346 | } else { 347 | this._lineCount++ 348 | 349 | switch (this.contentTransferEncoding.value) { 350 | case 'base64': 351 | this._bodyBuffer += line + terminator 352 | break 353 | case 'quoted-printable': { 354 | let curLine = this._lineRemainder + line + terminator 355 | const match = curLine.match(/=[a-f0-9]{0,1}$/i) 356 | if (match) { 357 | this._lineRemainder = match[0] 358 | curLine = curLine.substr(0, curLine.length - this._lineRemainder.length) 359 | } else { 360 | this._lineRemainder = '' 361 | } 362 | this._bodyBuffer += curLine 363 | break 364 | } 365 | case '7bit': 366 | case '8bit': 367 | default: 368 | this._bodyBuffer += line + terminator 369 | break 370 | } 371 | } 372 | } 373 | 374 | /** 375 | * Emits a chunk of the body 376 | */ 377 | _emitBody () { 378 | this._decodeBodyBuffer() 379 | if (this._isMultipart || !this._bodyBuffer) { 380 | return 381 | } 382 | 383 | this._processFlowedText() 384 | this.content = str2arr(this._bodyBuffer) 385 | this._processHtmlText() 386 | this._bodyBuffer = '' 387 | } 388 | 389 | _processFlowedText () { 390 | const isText = /^text\/(plain|html)$/i.test(this.contentType.value) 391 | const isFlowed = /^flowed$/i.test(pathOr('', ['contentType', 'params', 'format'])(this)) 392 | if (!isText || !isFlowed) return 393 | 394 | const delSp = /^yes$/i.test(this.contentType.params.delsp) 395 | let bodyBuffer = '' 396 | 397 | forEachLine(this._bodyBuffer, function (line, terminator) { 398 | // remove soft linebreaks after space symbols. 399 | // delsp adds spaces to text to be able to fold it. 400 | // these spaces can be removed once the text is unfolded 401 | const endsWithSpace = / $/.test(line) 402 | const isBoundary = /(^|\n)-- $/.test(line) 403 | 404 | bodyBuffer += (delSp ? line.replace(/[ ]+$/, '') : line) + ((endsWithSpace && !isBoundary) ? '' : terminator) 405 | }) 406 | 407 | this._bodyBuffer = bodyBuffer.replace(/^ /gm, '') // remove whitespace stuffing http://tools.ietf.org/html/rfc3676#section-4.4 408 | } 409 | 410 | _processHtmlText () { 411 | const contentDisposition = (this.headers['content-disposition'] && this.headers['content-disposition'][0]) || parseHeaderValue('') 412 | const isHtml = /^text\/(plain|html)$/i.test(this.contentType.value) 413 | const isAttachment = /^attachment$/i.test(contentDisposition.value) 414 | if (isHtml && !isAttachment) { 415 | if (!this.charset && /^text\/html$/i.test(this.contentType.value)) { 416 | this.charset = this.detectHTMLCharset(this._bodyBuffer) 417 | } 418 | 419 | // decode "binary" string to an unicode string 420 | if (!/^utf[-_]?8$/i.test(this.charset)) { 421 | this.content = convert(str2arr(this._bodyBuffer), this.charset || 'iso-8859-1') 422 | } else if (this.contentTransferEncoding.value === 'base64') { 423 | this.content = utf8Str2arr(this._bodyBuffer) 424 | } 425 | 426 | // override charset for text nodes 427 | this.charset = this.contentType.params.charset = 'utf-8' 428 | } 429 | } 430 | 431 | /** 432 | * Detect charset from a html file 433 | * 434 | * @param {String} html Input HTML 435 | * @returns {String} Charset if found or undefined 436 | */ 437 | detectHTMLCharset (html) { 438 | let charset, input 439 | 440 | html = html.replace(/\r?\n|\r/g, ' ') 441 | let meta = html.match(/]*?>/i) 442 | if (meta) { 443 | input = meta[0] 444 | } 445 | 446 | if (input) { 447 | charset = input.match(/charset\s?=\s?([a-zA-Z\-_:0-9]*);?/) 448 | if (charset) { 449 | charset = (charset[1] || '').trim().toLowerCase() 450 | } 451 | } 452 | 453 | meta = html.match(//\s]+)/i) 454 | if (!charset && meta) { 455 | charset = (meta[1] || '').trim().toLowerCase() 456 | } 457 | 458 | return charset 459 | } 460 | } 461 | 462 | const str2arr = str => new Uint8Array(str.split('').map(char => char.charCodeAt(0))) 463 | const utf8Str2arr = str => new TextEncoder('utf-8').encode(str) 464 | -------------------------------------------------------------------------------- /src/timezones.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'ACDT': '+1030', 3 | 'ACST': '+0930', 4 | 'ACT': '+0800', 5 | 'ADT': '-0300', 6 | 'AEDT': '+1100', 7 | 'AEST': '+1000', 8 | 'AFT': '+0430', 9 | 'AKDT': '-0800', 10 | 'AKST': '-0900', 11 | 'AMST': '-0300', 12 | 'AMT': '+0400', 13 | 'ART': '-0300', 14 | 'AST': '+0300', 15 | 'AWDT': '+0900', 16 | 'AWST': '+0800', 17 | 'AZOST': '-0100', 18 | 'AZT': '+0400', 19 | 'BDT': '+0800', 20 | 'BIOT': '+0600', 21 | 'BIT': '-1200', 22 | 'BOT': '-0400', 23 | 'BRT': '-0300', 24 | 'BST': '+0600', 25 | 'BTT': '+0600', 26 | 'CAT': '+0200', 27 | 'CCT': '+0630', 28 | 'CDT': '-0500', 29 | 'CEDT': '+0200', 30 | 'CEST': '+0200', 31 | 'CET': '+0100', 32 | 'CHADT': '+1345', 33 | 'CHAST': '+1245', 34 | 'CHOT': '+0800', 35 | 'CHST': '+1000', 36 | 'CHUT': '+1000', 37 | 'CIST': '-0800', 38 | 'CIT': '+0800', 39 | 'CKT': '-1000', 40 | 'CLST': '-0300', 41 | 'CLT': '-0400', 42 | 'COST': '-0400', 43 | 'COT': '-0500', 44 | 'CST': '-0600', 45 | 'CT': '+0800', 46 | 'CVT': '-0100', 47 | 'CWST': '+0845', 48 | 'CXT': '+0700', 49 | 'DAVT': '+0700', 50 | 'DDUT': '+1000', 51 | 'DFT': '+0100', 52 | 'EASST': '-0500', 53 | 'EAST': '-0600', 54 | 'EAT': '+0300', 55 | 'ECT': '-0500', 56 | 'EDT': '-0400', 57 | 'EEDT': '+0300', 58 | 'EEST': '+0300', 59 | 'EET': '+0200', 60 | 'EGST': '+0000', 61 | 'EGT': '-0100', 62 | 'EIT': '+0900', 63 | 'EST': '-0500', 64 | 'FET': '+0300', 65 | 'FJT': '+1200', 66 | 'FKST': '-0300', 67 | 'FKT': '-0400', 68 | 'FNT': '-0200', 69 | 'GALT': '-0600', 70 | 'GAMT': '-0900', 71 | 'GET': '+0400', 72 | 'GFT': '-0300', 73 | 'GILT': '+1200', 74 | 'GIT': '-0900', 75 | 'GMT': '+0000', 76 | 'GST': '+0400', 77 | 'GYT': '-0400', 78 | 'HADT': '-0900', 79 | 'HAEC': '+0200', 80 | 'HAST': '-1000', 81 | 'HKT': '+0800', 82 | 'HMT': '+0500', 83 | 'HOVT': '+0700', 84 | 'HST': '-1000', 85 | 'ICT': '+0700', 86 | 'IDT': '+0300', 87 | 'IOT': '+0300', 88 | 'IRDT': '+0430', 89 | 'IRKT': '+0900', 90 | 'IRST': '+0330', 91 | 'IST': '+0530', 92 | 'JST': '+0900', 93 | 'KGT': '+0600', 94 | 'KOST': '+1100', 95 | 'KRAT': '+0700', 96 | 'KST': '+0900', 97 | 'LHST': '+1030', 98 | 'LINT': '+1400', 99 | 'MAGT': '+1200', 100 | 'MART': '-0930', 101 | 'MAWT': '+0500', 102 | 'MDT': '-0600', 103 | 'MET': '+0100', 104 | 'MEST': '+0200', 105 | 'MHT': '+1200', 106 | 'MIST': '+1100', 107 | 'MIT': '-0930', 108 | 'MMT': '+0630', 109 | 'MSK': '+0400', 110 | 'MST': '-0700', 111 | 'MUT': '+0400', 112 | 'MVT': '+0500', 113 | 'MYT': '+0800', 114 | 'NCT': '+1100', 115 | 'NDT': '-0230', 116 | 'NFT': '+1130', 117 | 'NPT': '+0545', 118 | 'NST': '-0330', 119 | 'NT': '-0330', 120 | 'NUT': '-1100', 121 | 'NZDT': '+1300', 122 | 'NZST': '+1200', 123 | 'OMST': '+0700', 124 | 'ORAT': '+0500', 125 | 'PDT': '-0700', 126 | 'PET': '-0500', 127 | 'PETT': '+1200', 128 | 'PGT': '+1000', 129 | 'PHOT': '+1300', 130 | 'PHT': '+0800', 131 | 'PKT': '+0500', 132 | 'PMDT': '-0200', 133 | 'PMST': '-0300', 134 | 'PONT': '+1100', 135 | 'PST': '-0800', 136 | 'PYST': '-0300', 137 | 'PYT': '-0400', 138 | 'RET': '+0400', 139 | 'ROTT': '-0300', 140 | 'SAKT': '+1100', 141 | 'SAMT': '+0400', 142 | 'SAST': '+0200', 143 | 'SBT': '+1100', 144 | 'SCT': '+0400', 145 | 'SGT': '+0800', 146 | 'SLST': '+0530', 147 | 'SRT': '-0300', 148 | 'SST': '+0800', 149 | 'SYOT': '+0300', 150 | 'TAHT': '-1000', 151 | 'THA': '+0700', 152 | 'TFT': '+0500', 153 | 'TJT': '+0500', 154 | 'TKT': '+1300', 155 | 'TLT': '+0900', 156 | 'TMT': '+0500', 157 | 'TOT': '+1300', 158 | 'TVT': '+1200', 159 | 'UCT': '+0000', 160 | 'ULAT': '+0800', 161 | 'UTC': '+0000', 162 | 'UYST': '-0200', 163 | 'UYT': '-0300', 164 | 'UZT': '+0500', 165 | 'VET': '-0430', 166 | 'VLAT': '+1000', 167 | 'VOLT': '+0400', 168 | 'VOST': '+0600', 169 | 'VUT': '+1100', 170 | 'WAKT': '+1200', 171 | 'WAST': '+0200', 172 | 'WAT': '+0100', 173 | 'WEDT': '+0100', 174 | 'WEST': '+0100', 175 | 'WET': '+0000', 176 | 'WST': '+0800', 177 | 'YAKT': '+1000', 178 | 'YEKT': '+0600', 179 | 'Z': '+0000' 180 | } 181 | -------------------------------------------------------------------------------- /testutils.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import sinon from 'sinon' 3 | 4 | global.expect = expect 5 | global.sinon = sinon 6 | --------------------------------------------------------------------------------