├── .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 | [](https://greenkeeper.io/) [](https://travis-ci.org/emailjs/emailjs-mime-parser) [](https://standardjs.com) [](https://kangax.github.io/compat-table/es6/) [](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,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9taW1lcGFyc2VyLmpzIl0sIm5hbWVzIjpbInBhcnNlIiwiTUFYSU1VTV9OVU1CRVJfT0ZfTUlNRV9OT0RFUyIsIk5vZGVDb3VudGVyIiwiY291bnQiLCJFcnJvciIsImZvckVhY2hMaW5lIiwic3RyIiwiY2FsbGJhY2siLCJsaW5lIiwidGVybWluYXRvciIsImkiLCJsZW5ndGgiLCJjaGFyIiwibmV4dENoYXIiLCJjaHVuayIsInJvb3QiLCJNaW1lTm9kZSIsIlN0cmluZyIsImZyb21DaGFyQ29kZSIsImFwcGx5Iiwid3JpdGVMaW5lIiwiZmluYWxpemUiLCJub2RlQ291bnRlciIsImJ1bXAiLCJoZWFkZXIiLCJoZWFkZXJzIiwiYm9keXN0cnVjdHVyZSIsImNoaWxkTm9kZXMiLCJyYXciLCJfc3RhdGUiLCJfYm9keUJ1ZmZlciIsIl9saW5lQ291bnQiLCJfY3VycmVudENoaWxkIiwiX2xpbmVSZW1haW5kZXIiLCJfaXNNdWx0aXBhcnQiLCJfbXVsdGlwYXJ0Qm91bmRhcnkiLCJfaXNSZmM4MjIiLCJfcHJvY2Vzc0hlYWRlckxpbmUiLCJfcHJvY2Vzc0JvZHlMaW5lIiwiX2VtaXRCb2R5IiwicmVkdWNlIiwiYWdnIiwiY2hpbGQiLCJqb2luIiwiY29udGVudFRyYW5zZmVyRW5jb2RpbmciLCJ2YWx1ZSIsImNoYXJzZXQiLCJyZXBsYWNlIiwibSIsImNvZGUiLCJwYXJzZUludCIsIl9wYXJzZUhlYWRlcnMiLCJtYXRjaCIsInB1c2giLCJoYXNCaW5hcnkiLCJsZW4iLCJzcGxpdCIsImtleSIsInNoaWZ0IiwidHJpbSIsInRvTG93ZXJDYXNlIiwic3RyMmFyciIsImNvbmNhdCIsIl9wYXJzZUhlYWRlclZhbHVlIiwicGFyYW1zIiwiZmV0Y2hDb250ZW50VHlwZSIsIl9wcm9jZXNzQ29udGVudFRyYW5zZmVyRW5jb2RpbmciLCJwYXJzZWRWYWx1ZSIsImlzQWRkcmVzcyIsIl9wYXJzZURhdGUiLCJpbml0aWFsIiwiX2RlY29kZUhlYWRlckNoYXJzZXQiLCJkYXRlIiwiRGF0ZSIsInRpbWV6b25lIiwidHoiLCJ0b1VwcGVyQ2FzZSIsInRvU3RyaW5nIiwidG9VVENTdHJpbmciLCJwYXJzZWQiLCJPYmplY3QiLCJrZXlzIiwiZm9yRWFjaCIsIkFycmF5IiwiaXNBcnJheSIsImFkZHIiLCJuYW1lIiwiZ3JvdXAiLCJkZWZhdWx0VmFsdWUiLCJjb250ZW50VHlwZSIsInR5cGUiLCJib3VuZGFyeSIsInBvcCIsImRlZmF1bHRDb250ZW50RGlzcG9zaXRpb25WYWx1ZSIsImNvbnRlbnREaXNwb3NpdGlvbiIsImlzQXR0YWNobWVudCIsImlzSW5saW5lQXR0YWNobWVudCIsImN1ckxpbmUiLCJzdWJzdHIiLCJfZGVjb2RlQm9keUJ1ZmZlciIsIl9wcm9jZXNzRmxvd2VkVGV4dCIsImNvbnRlbnQiLCJfcHJvY2Vzc0h0bWxUZXh0IiwiaXNUZXh0IiwidGVzdCIsImlzRmxvd2VkIiwiZGVsU3AiLCJkZWxzcCIsImJvZHlCdWZmZXIiLCJlbmRzV2l0aFNwYWNlIiwiaXNCb3VuZGFyeSIsImlzSHRtbCIsImRldGVjdEhUTUxDaGFyc2V0IiwidXRmOFN0cjJhcnIiLCJodG1sIiwiaW5wdXQiLCJtZXRhIiwiVWludDhBcnJheSIsIm1hcCIsImNoYXJDb2RlQXQiLCJUZXh0RW5jb2RlciIsImVuY29kZSJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O2tCQW9Ed0JBLEs7O0FBcER4Qjs7QUFDQTs7OztBQUNBOztBQUNBOztBQUNBOzs7Ozs7OztBQUVBOzs7O0FBSUEsSUFBTUMsK0JBQStCLEdBQXJDOztJQUNhQyxXLFdBQUFBLFc7QUFDWCx5QkFBZTtBQUFBOztBQUNiLFNBQUtDLEtBQUwsR0FBYSxDQUFiO0FBQ0Q7Ozs7MkJBQ087QUFDTixVQUFJLEVBQUUsS0FBS0EsS0FBUCxHQUFlRiw0QkFBbkIsRUFBaUQ7QUFDL0MsY0FBTSxJQUFJRyxLQUFKLENBQVUsd0NBQVYsQ0FBTjtBQUNEO0FBQ0Y7Ozs7OztBQUdILFNBQVNDLFdBQVQsQ0FBc0JDLEdBQXRCLEVBQTJCQyxRQUEzQixFQUFxQztBQUNuQyxNQUFJQyxPQUFPLEVBQVg7QUFDQSxNQUFJQyxhQUFhLEVBQWpCO0FBQ0EsT0FBSyxJQUFJQyxJQUFJLENBQWIsRUFBZ0JBLElBQUlKLElBQUlLLE1BQXhCLEVBQWdDRCxLQUFLLENBQXJDLEVBQXdDO0FBQ3RDLFFBQU1FLE9BQU9OLElBQUlJLENBQUosQ0FBYjtBQUNBLFFBQUlFLFNBQVMsSUFBVCxJQUFpQkEsU0FBUyxJQUE5QixFQUFvQztBQUNsQyxVQUFNQyxXQUFXUCxJQUFJSSxJQUFJLENBQVIsQ0FBakI7QUFDQUQsb0JBQWNHLElBQWQ7QUFDQTtBQUNBLFVBQUtILGFBQWFJLFFBQWQsS0FBNEIsTUFBNUIsSUFBdUNKLGFBQWFJLFFBQWQsS0FBNEIsTUFBdEUsRUFBOEU7QUFDNUVOLGlCQUFTQyxJQUFULEVBQWVDLGFBQWFJLFFBQTVCO0FBQ0FMLGVBQU8sRUFBUDtBQUNBQyxxQkFBYSxFQUFiO0FBQ0FDLGFBQUssQ0FBTDtBQUNGO0FBQ0MsT0FORCxNQU1PLElBQUlELGVBQWUsSUFBZixJQUF1QkEsZUFBZSxJQUExQyxFQUFnRDtBQUNyREYsaUJBQVNDLElBQVQsRUFBZUMsVUFBZjtBQUNBRCxlQUFPLEVBQVA7QUFDQUMscUJBQWEsRUFBYjtBQUNEO0FBQ0YsS0FmRCxNQWVPO0FBQ0xELGNBQVFJLElBQVI7QUFDRDtBQUNGO0FBQ0Q7QUFDQSxNQUFJSixTQUFTLEVBQVQsSUFBZUMsZUFBZSxFQUFsQyxFQUFzQztBQUNwQ0YsYUFBU0MsSUFBVCxFQUFlQyxVQUFmO0FBQ0Q7QUFDRjs7QUFFYyxTQUFTVCxLQUFULENBQWdCYyxLQUFoQixFQUF1QjtBQUNwQyxNQUFNQyxPQUFPLElBQUlDLFFBQUosQ0FBYSxJQUFJZCxXQUFKLEVBQWIsQ0FBYjtBQUNBLE1BQU1JLE1BQU0sT0FBT1EsS0FBUCxLQUFpQixRQUFqQixHQUE0QkEsS0FBNUIsR0FBb0NHLE9BQU9DLFlBQVAsQ0FBb0JDLEtBQXBCLENBQTBCLElBQTFCLEVBQWdDTCxLQUFoQyxDQUFoRDtBQUNBVCxjQUFZQyxHQUFaLEVBQWlCLFVBQVVFLElBQVYsRUFBZ0JDLFVBQWhCLEVBQTRCO0FBQzNDTSxTQUFLSyxTQUFMLENBQWVaLElBQWYsRUFBcUJDLFVBQXJCO0FBQ0QsR0FGRDtBQUdBTSxPQUFLTSxRQUFMO0FBQ0EsU0FBT04sSUFBUDtBQUNEOztJQUVZQyxRLFdBQUFBLFE7QUFDWCxzQkFBOEM7QUFBQSxRQUFqQ00sV0FBaUMsdUVBQW5CLElBQUlwQixXQUFKLEVBQW1COztBQUFBOztBQUM1QyxTQUFLb0IsV0FBTCxHQUFtQkEsV0FBbkI7QUFDQSxTQUFLQSxXQUFMLENBQWlCQyxJQUFqQjs7QUFFQSxTQUFLQyxNQUFMLEdBQWMsRUFBZCxDQUo0QyxDQUkzQjtBQUNqQixTQUFLQyxPQUFMLEdBQWUsRUFBZixDQUw0QyxDQUsxQjtBQUNsQixTQUFLQyxhQUFMLEdBQXFCLEVBQXJCO0FBQ0EsU0FBS0MsVUFBTCxHQUFrQixFQUFsQixDQVA0QyxDQU92QjtBQUNyQixTQUFLQyxHQUFMLEdBQVcsRUFBWCxDQVI0QyxDQVE5Qjs7QUFFZCxTQUFLQyxNQUFMLEdBQWMsUUFBZCxDQVY0QyxDQVVyQjtBQUN2QixTQUFLQyxXQUFMLEdBQW1CLEVBQW5CLENBWDRDLENBV3RCO0FBQ3RCLFNBQUtDLFVBQUwsR0FBa0IsQ0FBbEIsQ0FaNEMsQ0FZeEI7QUFDcEIsU0FBS0MsYUFBTCxHQUFxQixLQUFyQixDQWI0QyxDQWFqQjtBQUMzQixTQUFLQyxjQUFMLEdBQXNCLEVBQXRCLENBZDRDLENBY25CO0FBQ3pCLFNBQUtDLFlBQUwsR0FBb0IsS0FBcEIsQ0FmNEMsQ0FlbEI7QUFDMUIsU0FBS0Msa0JBQUwsR0FBMEIsS0FBMUIsQ0FoQjRDLENBZ0JaO0FBQ2hDLFNBQUtDLFNBQUwsR0FBaUIsS0FBakIsQ0FqQjRDLENBaUJyQjtBQUN4Qjs7Ozs4QkFFVTVCLEksRUFBTUMsVSxFQUFZO0FBQzNCLFdBQUttQixHQUFMLElBQVlwQixRQUFRQyxjQUFjLElBQXRCLENBQVo7O0FBRUEsVUFBSSxLQUFLb0IsTUFBTCxLQUFnQixRQUFwQixFQUE4QjtBQUM1QixhQUFLUSxrQkFBTCxDQUF3QjdCLElBQXhCO0FBQ0QsT0FGRCxNQUVPLElBQUksS0FBS3FCLE1BQUwsS0FBZ0IsTUFBcEIsRUFBNEI7QUFDakMsYUFBS1MsZ0JBQUwsQ0FBc0I5QixJQUF0QixFQUE0QkMsVUFBNUI7QUFDRDtBQUNGOzs7K0JBRVc7QUFBQTs7QUFDVixVQUFJLEtBQUsyQixTQUFULEVBQW9CO0FBQ2xCLGFBQUtKLGFBQUwsQ0FBbUJYLFFBQW5CO0FBQ0QsT0FGRCxNQUVPO0FBQ0wsYUFBS2tCLFNBQUw7QUFDRDs7QUFFRCxXQUFLYixhQUFMLEdBQXFCLEtBQUtDLFVBQUwsQ0FDbEJhLE1BRGtCLENBQ1gsVUFBQ0MsR0FBRCxFQUFNQyxLQUFOO0FBQUEsZUFBZ0JELE1BQU0sSUFBTixHQUFhLE1BQUtOLGtCQUFsQixHQUF1QyxJQUF2QyxHQUE4Q08sTUFBTWhCLGFBQXBFO0FBQUEsT0FEVyxFQUN3RSxLQUFLRixNQUFMLENBQVltQixJQUFaLENBQWlCLElBQWpCLElBQXlCLE1BRGpHLEtBRWxCLEtBQUtSLGtCQUFMLEdBQTBCLE9BQU8sS0FBS0Esa0JBQVosR0FBaUMsTUFBM0QsR0FBb0UsRUFGbEQsQ0FBckI7QUFHRDs7O3dDQUVvQjtBQUNuQixjQUFRLEtBQUtTLHVCQUFMLENBQTZCQyxLQUFyQztBQUNFLGFBQUssUUFBTDtBQUNFLGVBQUtmLFdBQUwsR0FBbUIsb0NBQWEsS0FBS0EsV0FBbEIsRUFBK0IsS0FBS2dCLE9BQXBDLENBQW5CO0FBQ0E7QUFDRixhQUFLLGtCQUFMO0FBQXlCO0FBQ3ZCLGlCQUFLaEIsV0FBTCxHQUFtQixLQUFLQSxXQUFMLENBQ2hCaUIsT0FEZ0IsQ0FDUixhQURRLEVBQ08sRUFEUCxFQUVoQkEsT0FGZ0IsQ0FFUixrQkFGUSxFQUVZLFVBQUNDLENBQUQsRUFBSUMsSUFBSjtBQUFBLHFCQUFhaEMsT0FBT0MsWUFBUCxDQUFvQmdDLFNBQVNELElBQVQsRUFBZSxFQUFmLENBQXBCLENBQWI7QUFBQSxhQUZaLENBQW5CO0FBR0E7QUFDRDtBQVRIO0FBV0Q7O0FBRUQ7Ozs7Ozs7O3VDQUtvQnpDLEksRUFBTTtBQUN4QixVQUFJLENBQUNBLElBQUwsRUFBVztBQUNULGFBQUsyQyxhQUFMO0FBQ0EsYUFBS3pCLGFBQUwsSUFBc0IsS0FBS0YsTUFBTCxDQUFZbUIsSUFBWixDQUFpQixJQUFqQixJQUF5QixNQUEvQztBQUNBLGFBQUtkLE1BQUwsR0FBYyxNQUFkO0FBQ0E7QUFDRDs7QUFFRCxVQUFJckIsS0FBSzRDLEtBQUwsQ0FBVyxLQUFYLEtBQXFCLEtBQUs1QixNQUFMLENBQVliLE1BQXJDLEVBQTZDO0FBQzNDLGFBQUthLE1BQUwsQ0FBWSxLQUFLQSxNQUFMLENBQVliLE1BQVosR0FBcUIsQ0FBakMsS0FBdUMsT0FBT0gsSUFBOUM7QUFDRCxPQUZELE1BRU87QUFDTCxhQUFLZ0IsTUFBTCxDQUFZNkIsSUFBWixDQUFpQjdDLElBQWpCO0FBQ0Q7QUFDRjs7QUFFRDs7Ozs7O29DQUdpQjtBQUNmLFdBQUssSUFBSThDLFlBQVksS0FBaEIsRUFBdUI1QyxJQUFJLENBQTNCLEVBQThCNkMsTUFBTSxLQUFLL0IsTUFBTCxDQUFZYixNQUFyRCxFQUE2REQsSUFBSTZDLEdBQWpFLEVBQXNFN0MsR0FBdEUsRUFBMkU7QUFDekUsWUFBSW1DLFFBQVEsS0FBS3JCLE1BQUwsQ0FBWWQsQ0FBWixFQUFlOEMsS0FBZixDQUFxQixHQUFyQixDQUFaO0FBQ0EsWUFBTUMsTUFBTSxDQUFDWixNQUFNYSxLQUFOLE1BQWlCLEVBQWxCLEVBQXNCQyxJQUF0QixHQUE2QkMsV0FBN0IsRUFBWjtBQUNBZixnQkFBUSxDQUFDQSxNQUFNRixJQUFOLENBQVcsR0FBWCxLQUFtQixFQUFwQixFQUF3QkksT0FBeEIsQ0FBZ0MsS0FBaEMsRUFBdUMsRUFBdkMsRUFBMkNZLElBQTNDLEVBQVI7O0FBRUEsWUFBSWQsTUFBTU8sS0FBTixDQUFZLGlCQUFaLENBQUosRUFBb0M7QUFDbEMsY0FBSSxDQUFDLEtBQUtOLE9BQVYsRUFBbUI7QUFDakJRLHdCQUFZLElBQVo7QUFDRDtBQUNEO0FBQ0FULGtCQUFRLDhCQUFPLCtCQUFRZ0IsUUFBUWhCLEtBQVIsQ0FBUixFQUF3QixLQUFLQyxPQUFMLElBQWdCLFlBQXhDLENBQVAsQ0FBUjtBQUNEOztBQUVELGFBQUtyQixPQUFMLENBQWFnQyxHQUFiLElBQW9CLENBQUMsS0FBS2hDLE9BQUwsQ0FBYWdDLEdBQWIsS0FBcUIsRUFBdEIsRUFBMEJLLE1BQTFCLENBQWlDLENBQUMsS0FBS0MsaUJBQUwsQ0FBdUJOLEdBQXZCLEVBQTRCWixLQUE1QixDQUFELENBQWpDLENBQXBCOztBQUVBLFlBQUksQ0FBQyxLQUFLQyxPQUFOLElBQWlCVyxRQUFRLGNBQTdCLEVBQTZDO0FBQzNDLGVBQUtYLE9BQUwsR0FBZSxLQUFLckIsT0FBTCxDQUFhZ0MsR0FBYixFQUFrQixLQUFLaEMsT0FBTCxDQUFhZ0MsR0FBYixFQUFrQjlDLE1BQWxCLEdBQTJCLENBQTdDLEVBQWdEcUQsTUFBaEQsQ0FBdURsQixPQUF0RTtBQUNEOztBQUVELFlBQUlRLGFBQWEsS0FBS1IsT0FBdEIsRUFBK0I7QUFDN0I7QUFDQVEsc0JBQVksS0FBWjtBQUNBLGVBQUs3QixPQUFMLEdBQWUsRUFBZjtBQUNBZixjQUFJLENBQUMsQ0FBTCxDQUo2QixDQUl0QjtBQUNSO0FBQ0Y7O0FBRUQsV0FBS3VELGdCQUFMO0FBQ0EsV0FBS0MsK0JBQUw7QUFDRDs7QUFFRDs7Ozs7Ozs7O3NDQU1tQlQsRyxFQUFLWixLLEVBQU87QUFDN0IsVUFBSXNCLG9CQUFKO0FBQ0EsVUFBSUMsWUFBWSxLQUFoQjs7QUFFQSxjQUFRWCxHQUFSO0FBQ0UsYUFBSyxjQUFMO0FBQ0EsYUFBSywyQkFBTDtBQUNBLGFBQUsscUJBQUw7QUFDQSxhQUFLLGdCQUFMO0FBQ0VVLHdCQUFjLHdDQUFpQnRCLEtBQWpCLENBQWQ7QUFDQTtBQUNGLGFBQUssTUFBTDtBQUNBLGFBQUssUUFBTDtBQUNBLGFBQUssSUFBTDtBQUNBLGFBQUssVUFBTDtBQUNBLGFBQUssSUFBTDtBQUNBLGFBQUssS0FBTDtBQUNBLGFBQUssa0JBQUw7QUFDQSxhQUFLLFdBQUw7QUFDQSxhQUFLLGFBQUw7QUFDQSxhQUFLLGNBQUw7QUFDRXVCLHNCQUFZLElBQVo7QUFDQUQsd0JBQWM7QUFDWnRCLG1CQUFPLEdBQUdpQixNQUFILENBQVUsb0NBQWFqQixLQUFiLEtBQXVCLEVBQWpDO0FBREssV0FBZDtBQUdBO0FBQ0YsYUFBSyxNQUFMO0FBQ0VzQix3QkFBYztBQUNadEIsbUJBQU8sS0FBS3dCLFVBQUwsQ0FBZ0J4QixLQUFoQjtBQURLLFdBQWQ7QUFHQTtBQUNGO0FBQ0VzQix3QkFBYztBQUNadEIsbUJBQU9BO0FBREssV0FBZDtBQTVCSjtBQWdDQXNCLGtCQUFZRyxPQUFaLEdBQXNCekIsS0FBdEI7O0FBRUEsV0FBSzBCLG9CQUFMLENBQTBCSixXQUExQixFQUF1QyxFQUFFQyxvQkFBRixFQUF2Qzs7QUFFQSxhQUFPRCxXQUFQO0FBQ0Q7O0FBRUQ7Ozs7Ozs7Ozs7aUNBT3NCO0FBQUEsVUFBVjdELEdBQVUsdUVBQUosRUFBSTs7QUFDcEIsVUFBTWtFLE9BQU8sSUFBSUMsSUFBSixDQUFTbkUsSUFBSXFELElBQUosR0FBV1osT0FBWCxDQUFtQixZQUFuQixFQUFpQztBQUFBLGVBQU0yQixvQkFBU0MsR0FBR0MsV0FBSCxFQUFULEtBQThCLE9BQXBDO0FBQUEsT0FBakMsQ0FBVCxDQUFiO0FBQ0EsYUFBUUosS0FBS0ssUUFBTCxPQUFvQixjQUFyQixHQUF1Q0wsS0FBS00sV0FBTCxHQUFtQi9CLE9BQW5CLENBQTJCLEtBQTNCLEVBQWtDLE9BQWxDLENBQXZDLEdBQW9GekMsR0FBM0Y7QUFDRDs7O3lDQUVxQnlFLE0sRUFBNEI7QUFBQTs7QUFBQSxxRkFBSixFQUFJO0FBQUEsVUFBbEJYLFNBQWtCLFFBQWxCQSxTQUFrQjs7QUFDaEQ7QUFDQSxVQUFJLE9BQU9XLE9BQU9sQyxLQUFkLEtBQXdCLFFBQTVCLEVBQXNDO0FBQ3BDa0MsZUFBT2xDLEtBQVAsR0FBZSx1Q0FBZ0JrQyxPQUFPbEMsS0FBdkIsQ0FBZjtBQUNEOztBQUVEO0FBQ0FtQyxhQUFPQyxJQUFQLENBQVlGLE9BQU9mLE1BQVAsSUFBaUIsRUFBN0IsRUFBaUNrQixPQUFqQyxDQUF5QyxVQUFVekIsR0FBVixFQUFlO0FBQ3RELFlBQUksT0FBT3NCLE9BQU9mLE1BQVAsQ0FBY1AsR0FBZCxDQUFQLEtBQThCLFFBQWxDLEVBQTRDO0FBQzFDc0IsaUJBQU9mLE1BQVAsQ0FBY1AsR0FBZCxJQUFxQix1Q0FBZ0JzQixPQUFPZixNQUFQLENBQWNQLEdBQWQsQ0FBaEIsQ0FBckI7QUFDRDtBQUNGLE9BSkQ7O0FBTUE7QUFDQSxVQUFJVyxhQUFhZSxNQUFNQyxPQUFOLENBQWNMLE9BQU9sQyxLQUFyQixDQUFqQixFQUE4QztBQUM1Q2tDLGVBQU9sQyxLQUFQLENBQWFxQyxPQUFiLENBQXFCLGdCQUFRO0FBQzNCLGNBQUlHLEtBQUtDLElBQVQsRUFBZTtBQUNiRCxpQkFBS0MsSUFBTCxHQUFZLHVDQUFnQkQsS0FBS0MsSUFBckIsQ0FBWjtBQUNBLGdCQUFJSCxNQUFNQyxPQUFOLENBQWNDLEtBQUtFLEtBQW5CLENBQUosRUFBK0I7QUFDN0IscUJBQUtoQixvQkFBTCxDQUEwQixFQUFFMUIsT0FBT3dDLEtBQUtFLEtBQWQsRUFBMUIsRUFBaUQsRUFBRW5CLFdBQVcsSUFBYixFQUFqRDtBQUNEO0FBQ0Y7QUFDRixTQVBEO0FBUUQ7O0FBRUQsYUFBT1csTUFBUDtBQUNEOztBQUVEOzs7Ozs7dUNBR29CO0FBQ2xCLFVBQU1TLGVBQWUsd0NBQWlCLFlBQWpCLENBQXJCO0FBQ0EsV0FBS0MsV0FBTCxHQUFtQixtQkFBT0QsWUFBUCxFQUFxQixDQUFDLFNBQUQsRUFBWSxjQUFaLEVBQTRCLEdBQTVCLENBQXJCLEVBQXVELElBQXZELENBQW5CO0FBQ0EsV0FBS0MsV0FBTCxDQUFpQjVDLEtBQWpCLEdBQXlCLENBQUMsS0FBSzRDLFdBQUwsQ0FBaUI1QyxLQUFqQixJQUEwQixFQUEzQixFQUErQmUsV0FBL0IsR0FBNkNELElBQTdDLEVBQXpCO0FBQ0EsV0FBSzhCLFdBQUwsQ0FBaUJDLElBQWpCLEdBQXlCLEtBQUtELFdBQUwsQ0FBaUI1QyxLQUFqQixDQUF1QlcsS0FBdkIsQ0FBNkIsR0FBN0IsRUFBa0NFLEtBQWxDLE1BQTZDLE1BQXRFOztBQUVBLFVBQUksS0FBSytCLFdBQUwsQ0FBaUJ6QixNQUFqQixJQUEyQixLQUFLeUIsV0FBTCxDQUFpQnpCLE1BQWpCLENBQXdCbEIsT0FBbkQsSUFBOEQsQ0FBQyxLQUFLQSxPQUF4RSxFQUFpRjtBQUMvRSxhQUFLQSxPQUFMLEdBQWUsS0FBSzJDLFdBQUwsQ0FBaUJ6QixNQUFqQixDQUF3QmxCLE9BQXZDO0FBQ0Q7O0FBRUQsVUFBSSxLQUFLMkMsV0FBTCxDQUFpQkMsSUFBakIsS0FBMEIsV0FBMUIsSUFBeUMsS0FBS0QsV0FBTCxDQUFpQnpCLE1BQWpCLENBQXdCMkIsUUFBckUsRUFBK0U7QUFDN0UsYUFBS2hFLFVBQUwsR0FBa0IsRUFBbEI7QUFDQSxhQUFLTyxZQUFMLEdBQXFCLEtBQUt1RCxXQUFMLENBQWlCNUMsS0FBakIsQ0FBdUJXLEtBQXZCLENBQTZCLEdBQTdCLEVBQWtDb0MsR0FBbEMsTUFBMkMsT0FBaEU7QUFDQSxhQUFLekQsa0JBQUwsR0FBMEIsS0FBS3NELFdBQUwsQ0FBaUJ6QixNQUFqQixDQUF3QjJCLFFBQWxEO0FBQ0Q7O0FBRUQ7Ozs7O0FBS0EsVUFBTUUsaUNBQWlDLHdDQUFpQixFQUFqQixDQUF2QztBQUNBLFVBQU1DLHFCQUFxQixtQkFBT0QsOEJBQVAsRUFBdUMsQ0FBQyxTQUFELEVBQVkscUJBQVosRUFBbUMsR0FBbkMsQ0FBdkMsRUFBZ0YsSUFBaEYsQ0FBM0I7QUFDQSxVQUFNRSxlQUFlLENBQUNELG1CQUFtQmpELEtBQW5CLElBQTRCLEVBQTdCLEVBQWlDZSxXQUFqQyxHQUErQ0QsSUFBL0MsT0FBMEQsWUFBL0U7QUFDQSxVQUFNcUMscUJBQXFCLENBQUNGLG1CQUFtQmpELEtBQW5CLElBQTRCLEVBQTdCLEVBQWlDZSxXQUFqQyxHQUErQ0QsSUFBL0MsT0FBMEQsUUFBckY7QUFDQSxVQUFJLENBQUNvQyxnQkFBZ0JDLGtCQUFqQixLQUF3QyxLQUFLUCxXQUFMLENBQWlCQyxJQUFqQixLQUEwQixNQUFsRSxJQUE0RSxDQUFDLEtBQUs1QyxPQUF0RixFQUErRjtBQUM3RixhQUFLQSxPQUFMLEdBQWUsUUFBZjtBQUNEOztBQUVELFVBQUksS0FBSzJDLFdBQUwsQ0FBaUI1QyxLQUFqQixLQUEyQixnQkFBM0IsSUFBK0MsQ0FBQ2tELFlBQXBELEVBQWtFO0FBQ2hFOzs7O0FBSUEsYUFBSy9ELGFBQUwsR0FBcUIsSUFBSWhCLFFBQUosQ0FBYSxLQUFLTSxXQUFsQixDQUFyQjtBQUNBLGFBQUtLLFVBQUwsR0FBa0IsQ0FBQyxLQUFLSyxhQUFOLENBQWxCO0FBQ0EsYUFBS0ksU0FBTCxHQUFpQixJQUFqQjtBQUNEO0FBQ0Y7O0FBRUQ7Ozs7Ozs7c0RBSW1DO0FBQ2pDLFVBQU1vRCxlQUFlLHdDQUFpQixNQUFqQixDQUFyQjtBQUNBLFdBQUs1Qyx1QkFBTCxHQUErQixtQkFBTzRDLFlBQVAsRUFBcUIsQ0FBQyxTQUFELEVBQVksMkJBQVosRUFBeUMsR0FBekMsQ0FBckIsRUFBb0UsSUFBcEUsQ0FBL0I7QUFDQSxXQUFLNUMsdUJBQUwsQ0FBNkJDLEtBQTdCLEdBQXFDLG1CQUFPLEVBQVAsRUFBVyxDQUFDLHlCQUFELEVBQTRCLE9BQTVCLENBQVgsRUFBaUQsSUFBakQsRUFBdURlLFdBQXZELEdBQXFFRCxJQUFyRSxFQUFyQztBQUNEOztBQUVEOzs7Ozs7Ozs7O3FDQU9rQm5ELEksRUFBTUMsVSxFQUFZO0FBQ2xDLFVBQUksS0FBS3lCLFlBQVQsRUFBdUI7QUFDckIsWUFBSTFCLFNBQVMsT0FBTyxLQUFLMkIsa0JBQXpCLEVBQTZDO0FBQzNDLGVBQUtULGFBQUwsSUFBc0JsQixPQUFPLElBQTdCO0FBQ0EsY0FBSSxLQUFLd0IsYUFBVCxFQUF3QjtBQUN0QixpQkFBS0EsYUFBTCxDQUFtQlgsUUFBbkI7QUFDRDtBQUNELGVBQUtXLGFBQUwsR0FBcUIsSUFBSWhCLFFBQUosQ0FBYSxLQUFLTSxXQUFsQixDQUFyQjtBQUNBLGVBQUtLLFVBQUwsQ0FBZ0IwQixJQUFoQixDQUFxQixLQUFLckIsYUFBMUI7QUFDRCxTQVBELE1BT08sSUFBSXhCLFNBQVMsT0FBTyxLQUFLMkIsa0JBQVosR0FBaUMsSUFBOUMsRUFBb0Q7QUFDekQsZUFBS1QsYUFBTCxJQUFzQmxCLE9BQU8sSUFBN0I7QUFDQSxjQUFJLEtBQUt3QixhQUFULEVBQXdCO0FBQ3RCLGlCQUFLQSxhQUFMLENBQW1CWCxRQUFuQjtBQUNEO0FBQ0QsZUFBS1csYUFBTCxHQUFxQixLQUFyQjtBQUNELFNBTk0sTUFNQSxJQUFJLEtBQUtBLGFBQVQsRUFBd0I7QUFDN0IsZUFBS0EsYUFBTCxDQUFtQlosU0FBbkIsQ0FBNkJaLElBQTdCLEVBQW1DQyxVQUFuQztBQUNELFNBRk0sTUFFQTtBQUNMO0FBQ0Q7QUFDRixPQW5CRCxNQW1CTyxJQUFJLEtBQUsyQixTQUFULEVBQW9CO0FBQ3pCLGFBQUtKLGFBQUwsQ0FBbUJaLFNBQW5CLENBQTZCWixJQUE3QixFQUFtQ0MsVUFBbkM7QUFDRCxPQUZNLE1BRUE7QUFDTCxhQUFLc0IsVUFBTDs7QUFFQSxnQkFBUSxLQUFLYSx1QkFBTCxDQUE2QkMsS0FBckM7QUFDRSxlQUFLLFFBQUw7QUFDRSxpQkFBS2YsV0FBTCxJQUFvQnRCLE9BQU9DLFVBQTNCO0FBQ0E7QUFDRixlQUFLLGtCQUFMO0FBQXlCO0FBQ3ZCLGtCQUFJd0YsVUFBVSxLQUFLaEUsY0FBTCxHQUFzQnpCLElBQXRCLEdBQTZCQyxVQUEzQztBQUNBLGtCQUFNMkMsUUFBUTZDLFFBQVE3QyxLQUFSLENBQWMsa0JBQWQsQ0FBZDtBQUNBLGtCQUFJQSxLQUFKLEVBQVc7QUFDVCxxQkFBS25CLGNBQUwsR0FBc0JtQixNQUFNLENBQU4sQ0FBdEI7QUFDQTZDLDBCQUFVQSxRQUFRQyxNQUFSLENBQWUsQ0FBZixFQUFrQkQsUUFBUXRGLE1BQVIsR0FBaUIsS0FBS3NCLGNBQUwsQ0FBb0J0QixNQUF2RCxDQUFWO0FBQ0QsZUFIRCxNQUdPO0FBQ0wscUJBQUtzQixjQUFMLEdBQXNCLEVBQXRCO0FBQ0Q7QUFDRCxtQkFBS0gsV0FBTCxJQUFvQm1FLE9BQXBCO0FBQ0E7QUFDRDtBQUNELGVBQUssTUFBTDtBQUNBLGVBQUssTUFBTDtBQUNBO0FBQ0UsaUJBQUtuRSxXQUFMLElBQW9CdEIsT0FBT0MsVUFBM0I7QUFDQTtBQXBCSjtBQXNCRDtBQUNGOztBQUVEOzs7Ozs7Z0NBR2E7QUFDWCxXQUFLMEYsaUJBQUw7QUFDQSxVQUFJLEtBQUtqRSxZQUFMLElBQXFCLENBQUMsS0FBS0osV0FBL0IsRUFBNEM7QUFDMUM7QUFDRDs7QUFFRCxXQUFLc0Usa0JBQUw7QUFDQSxXQUFLQyxPQUFMLEdBQWV4QyxRQUFRLEtBQUsvQixXQUFiLENBQWY7QUFDQSxXQUFLd0UsZ0JBQUw7QUFDQSxXQUFLeEUsV0FBTCxHQUFtQixFQUFuQjtBQUNEOzs7eUNBRXFCO0FBQ3BCLFVBQU15RSxTQUFTLHdCQUF3QkMsSUFBeEIsQ0FBNkIsS0FBS2YsV0FBTCxDQUFpQjVDLEtBQTlDLENBQWY7QUFDQSxVQUFNNEQsV0FBVyxZQUFZRCxJQUFaLENBQWlCLG1CQUFPLEVBQVAsRUFBVyxDQUFDLGFBQUQsRUFBZ0IsUUFBaEIsRUFBMEIsUUFBMUIsQ0FBWCxFQUFnRCxJQUFoRCxDQUFqQixDQUFqQjtBQUNBLFVBQUksQ0FBQ0QsTUFBRCxJQUFXLENBQUNFLFFBQWhCLEVBQTBCOztBQUUxQixVQUFNQyxRQUFRLFNBQVNGLElBQVQsQ0FBYyxLQUFLZixXQUFMLENBQWlCekIsTUFBakIsQ0FBd0IyQyxLQUF0QyxDQUFkO0FBQ0EsVUFBSUMsYUFBYSxFQUFqQjs7QUFFQXZHLGtCQUFZLEtBQUt5QixXQUFqQixFQUE4QixVQUFVdEIsSUFBVixFQUFnQkMsVUFBaEIsRUFBNEI7QUFDeEQ7QUFDQTtBQUNBO0FBQ0EsWUFBTW9HLGdCQUFnQixLQUFLTCxJQUFMLENBQVVoRyxJQUFWLENBQXRCO0FBQ0EsWUFBTXNHLGFBQWEsYUFBYU4sSUFBYixDQUFrQmhHLElBQWxCLENBQW5COztBQUVBb0csc0JBQWMsQ0FBQ0YsUUFBUWxHLEtBQUt1QyxPQUFMLENBQWEsT0FBYixFQUFzQixFQUF0QixDQUFSLEdBQW9DdkMsSUFBckMsS0FBK0NxRyxpQkFBaUIsQ0FBQ0MsVUFBbkIsR0FBaUMsRUFBakMsR0FBc0NyRyxVQUFwRixDQUFkO0FBQ0QsT0FSRDs7QUFVQSxXQUFLcUIsV0FBTCxHQUFtQjhFLFdBQVc3RCxPQUFYLENBQW1CLE1BQW5CLEVBQTJCLEVBQTNCLENBQW5CLENBbEJvQixDQWtCOEI7QUFDbkQ7Ozt1Q0FFbUI7QUFDbEIsVUFBTStDLHFCQUFzQixLQUFLckUsT0FBTCxDQUFhLHFCQUFiLEtBQXVDLEtBQUtBLE9BQUwsQ0FBYSxxQkFBYixFQUFvQyxDQUFwQyxDQUF4QyxJQUFtRix3Q0FBaUIsRUFBakIsQ0FBOUc7QUFDQSxVQUFNc0YsU0FBUyx3QkFBd0JQLElBQXhCLENBQTZCLEtBQUtmLFdBQUwsQ0FBaUI1QyxLQUE5QyxDQUFmO0FBQ0EsVUFBTWtELGVBQWUsZ0JBQWdCUyxJQUFoQixDQUFxQlYsbUJBQW1CakQsS0FBeEMsQ0FBckI7QUFDQSxVQUFJa0UsVUFBVSxDQUFDaEIsWUFBZixFQUE2QjtBQUMzQixZQUFJLENBQUMsS0FBS2pELE9BQU4sSUFBaUIsZ0JBQWdCMEQsSUFBaEIsQ0FBcUIsS0FBS2YsV0FBTCxDQUFpQjVDLEtBQXRDLENBQXJCLEVBQW1FO0FBQ2pFLGVBQUtDLE9BQUwsR0FBZSxLQUFLa0UsaUJBQUwsQ0FBdUIsS0FBS2xGLFdBQTVCLENBQWY7QUFDRDs7QUFFRDtBQUNBLFlBQUksQ0FBQyxlQUFlMEUsSUFBZixDQUFvQixLQUFLMUQsT0FBekIsQ0FBTCxFQUF3QztBQUN0QyxlQUFLdUQsT0FBTCxHQUFlLCtCQUFReEMsUUFBUSxLQUFLL0IsV0FBYixDQUFSLEVBQW1DLEtBQUtnQixPQUFMLElBQWdCLFlBQW5ELENBQWY7QUFDRCxTQUZELE1BRU8sSUFBSSxLQUFLRix1QkFBTCxDQUE2QkMsS0FBN0IsS0FBdUMsUUFBM0MsRUFBcUQ7QUFDMUQsZUFBS3dELE9BQUwsR0FBZVksWUFBWSxLQUFLbkYsV0FBakIsQ0FBZjtBQUNEOztBQUVEO0FBQ0EsYUFBS2dCLE9BQUwsR0FBZSxLQUFLMkMsV0FBTCxDQUFpQnpCLE1BQWpCLENBQXdCbEIsT0FBeEIsR0FBa0MsT0FBakQ7QUFDRDtBQUNGOztBQUVEOzs7Ozs7Ozs7c0NBTW1Cb0UsSSxFQUFNO0FBQ3ZCLFVBQUlwRSxnQkFBSjtBQUFBLFVBQWFxRSxjQUFiOztBQUVBRCxhQUFPQSxLQUFLbkUsT0FBTCxDQUFhLFdBQWIsRUFBMEIsR0FBMUIsQ0FBUDtBQUNBLFVBQUlxRSxPQUFPRixLQUFLOUQsS0FBTCxDQUFXLGdEQUFYLENBQVg7QUFDQSxVQUFJZ0UsSUFBSixFQUFVO0FBQ1JELGdCQUFRQyxLQUFLLENBQUwsQ0FBUjtBQUNEOztBQUVELFVBQUlELEtBQUosRUFBVztBQUNUckUsa0JBQVVxRSxNQUFNL0QsS0FBTixDQUFZLG9DQUFaLENBQVY7QUFDQSxZQUFJTixPQUFKLEVBQWE7QUFDWEEsb0JBQVUsQ0FBQ0EsUUFBUSxDQUFSLEtBQWMsRUFBZixFQUFtQmEsSUFBbkIsR0FBMEJDLFdBQTFCLEVBQVY7QUFDRDtBQUNGOztBQUVEd0QsYUFBT0YsS0FBSzlELEtBQUwsQ0FBVyx1Q0FBWCxDQUFQO0FBQ0EsVUFBSSxDQUFDTixPQUFELElBQVlzRSxJQUFoQixFQUFzQjtBQUNwQnRFLGtCQUFVLENBQUNzRSxLQUFLLENBQUwsS0FBVyxFQUFaLEVBQWdCekQsSUFBaEIsR0FBdUJDLFdBQXZCLEVBQVY7QUFDRDs7QUFFRCxhQUFPZCxPQUFQO0FBQ0Q7Ozs7OztBQUdILElBQU1lLFVBQVUsU0FBVkEsT0FBVTtBQUFBLFNBQU8sSUFBSXdELFVBQUosQ0FBZS9HLElBQUlrRCxLQUFKLENBQVUsRUFBVixFQUFjOEQsR0FBZCxDQUFrQjtBQUFBLFdBQVExRyxLQUFLMkcsVUFBTCxDQUFnQixDQUFoQixDQUFSO0FBQUEsR0FBbEIsQ0FBZixDQUFQO0FBQUEsQ0FBaEI7QUFDQSxJQUFNTixjQUFjLFNBQWRBLFdBQWM7QUFBQSxTQUFPLElBQUlPLHlCQUFKLENBQWdCLE9BQWhCLEVBQXlCQyxNQUF6QixDQUFnQ25ILEdBQWhDLENBQVA7QUFBQSxDQUFwQiIsImZpbGUiOiJtaW1lcGFyc2VyLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgcGF0aE9yIH0gZnJvbSAncmFtZGEnXG5pbXBvcnQgdGltZXpvbmUgZnJvbSAnLi90aW1lem9uZXMnXG5pbXBvcnQgeyBkZWNvZGUsIGJhc2U2NERlY29kZSwgY29udmVydCwgcGFyc2VIZWFkZXJWYWx1ZSwgbWltZVdvcmRzRGVjb2RlIH0gZnJvbSAnZW1haWxqcy1taW1lLWNvZGVjJ1xuaW1wb3J0IHsgVGV4dEVuY29kZXIgfSBmcm9tICd0ZXh0LWVuY29kaW5nJ1xuaW1wb3J0IHBhcnNlQWRkcmVzcyBmcm9tICdlbWFpbGpzLWFkZHJlc3NwYXJzZXInXG5cbi8qXG4gKiBDb3VudHMgTUlNRSBub2RlcyB0byBwcmV2ZW50IG1lbW9yeSBleGhhdXN0aW9uIGF0dGFja3MgKENXRS00MDApXG4gKiBzZWU6IGh0dHBzOi8vc255ay5pby92dWxuL25wbTplbWFpbGpzLW1pbWUtcGFyc2VyOjIwMTgwNjI1XG4gKi9cbmNvbnN0IE1BWElNVU1fTlVNQkVSX09GX01JTUVfTk9ERVMgPSA5OTlcbmV4cG9ydCBjbGFzcyBOb2RlQ291bnRlciB7XG4gIGNvbnN0cnVjdG9yICgpIHtcbiAgICB0aGlzLmNvdW50ID0gMFxuICB9XG4gIGJ1bXAgKCkge1xuICAgIGlmICgrK3RoaXMuY291bnQgPiBNQVhJTVVNX05VTUJFUl9PRl9NSU1FX05PREVTKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ01heGltdW0gbnVtYmVyIG9mIE1JTUUgbm9kZXMgZXhjZWVkZWQhJylcbiAgICB9XG4gIH1cbn1cblxuZnVuY3Rpb24gZm9yRWFjaExpbmUgKHN0ciwgY2FsbGJhY2spIHtcbiAgbGV0IGxpbmUgPSAnJ1xuICBsZXQgdGVybWluYXRvciA9ICcnXG4gIGZvciAodmFyIGkgPSAwOyBpIDwgc3RyLmxlbmd0aDsgaSArPSAxKSB7XG4gICAgY29uc3QgY2hhciA9IHN0cltpXVxuICAgIGlmIChjaGFyID09PSAnXFxyJyB8fCBjaGFyID09PSAnXFxuJykge1xuICAgICAgY29uc3QgbmV4dENoYXIgPSBzdHJbaSArIDFdXG4gICAgICB0ZXJtaW5hdG9yICs9IGNoYXJcbiAgICAgIC8vIERldGVjdCBXaW5kb3dzIGFuZCBNYWNpbnRvc2ggbGluZSB0ZXJtaW5hdG9ycy5cbiAgICAgIGlmICgodGVybWluYXRvciArIG5leHRDaGFyKSA9PT0gJ1xcclxcbicgfHwgKHRlcm1pbmF0b3IgKyBuZXh0Q2hhcikgPT09ICdcXG5cXHInKSB7XG4gICAgICAgIGNhbGxiYWNrKGxpbmUsIHRlcm1pbmF0b3IgKyBuZXh0Q2hhcilcbiAgICAgICAgbGluZSA9ICcnXG4gICAgICAgIHRlcm1pbmF0b3IgPSAnJ1xuICAgICAgICBpICs9IDFcbiAgICAgIC8vIERldGVjdCBzaW5nbGUtY2hhcmFjdGVyIHRlcm1pbmF0b3JzLCBsaWtlIExpbnV4IG9yIG90aGVyIHN5c3RlbS5cbiAgICAgIH0gZWxzZSBpZiAodGVybWluYXRvciA9PT0gJ1xcbicgfHwgdGVybWluYXRvciA9PT0gJ1xccicpIHtcbiAgICAgICAgY2FsbGJhY2sobGluZSwgdGVybWluYXRvcilcbiAgICAgICAgbGluZSA9ICcnXG4gICAgICAgIHRlcm1pbmF0b3IgPSAnJ1xuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBsaW5lICs9IGNoYXJcbiAgICB9XG4gIH1cbiAgLy8gRmx1c2ggdGhlIGxpbmUgYW5kIHRlcm1pbmF0b3IgdmFsdWVzIGlmIG5lY2Vzc2FyeTsgaGFuZGxlIGVkZ2UgY2FzZXMgd2hlcmUgTUlNRSBpcyBnZW5lcmF0ZWQgd2l0aG91dCBsYXN0IGxpbmUgdGVybWluYXRvci5cbiAgaWYgKGxpbmUgIT09ICcnIHx8IHRlcm1pbmF0b3IgIT09ICcnKSB7XG4gICAgY2FsbGJhY2sobGluZSwgdGVybWluYXRvcilcbiAgfVxufVxuXG5leHBvcnQgZGVmYXVsdCBmdW5jdGlvbiBwYXJzZSAoY2h1bmspIHtcbiAgY29uc3Qgcm9vdCA9IG5ldyBNaW1lTm9kZShuZXcgTm9kZUNvdW50ZXIoKSlcbiAgY29uc3Qgc3RyID0gdHlwZW9mIGNodW5rID09PSAnc3RyaW5nJyA/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 |
--------------------------------------------------------------------------------