Fixed the code just need to dress it up, update the comments, create the test cases

This commit is contained in:
Brian Whitney 2018-12-16 20:35:47 -05:00
parent 1d0d539797
commit 5f28349769
2 changed files with 184 additions and 257 deletions

View file

@ -14,266 +14,128 @@ import Utils from "../Utils";
* NOTE: Liberties taken include:
* No checks are made to verify quoted words are valid encodings e.g. underscore vs escape
* This attempts to decode mime reguardless if it is \r\n (correct newline) or \n (incorrect)
* Both Base64 and QuotedPrintable is used for decode. UUEncode is not available right now
* and is a standardized encoding format.
* Both Base64 and QuotedPrintable is used for decode.
*/
class Mime {
/**
* Internet MessageFormat constructor
* Mime Constructor
*/
constructor(input) {
this.mimeObj = Mime._parseMime(input);
}
/**
* Basic Email Parser that displays the header and mime sections as files.
* Args 0 boolean decode quoted words
*
* @param {string} input
* @param {boolean} decodeWords
* @returns {File[]}
*/
decodeMime(decodeWords) {
if (!this.mimeObj) {
return [];
}
//const emlObj = Mime._splitParseHead(this.input);
//if (!emlObj.body) {
// throw new OperationError("No body was found");
//}
if (decodeWords) {
emlObj.rawHeader = Mime.replaceEncodedWord(emlObj.rawHeader);
}
//const retval = [new File([emlObj.rawHeader], "Header", {type: "text/plain"})];
//let testval = Mime._parseMime(this.input);
testval.forEach(function(fileObj){
let name = fileObj.name;
if (fileObj.name === null) {
if (emlObj.header.hasOwnProperty("subject")) {
name = emlObj.header.subject[0];
} else {
name = "Undefined";
}
name = name.concat(Mime.getFileExt(fileObj.type));
}
retval.push(new File([Uint8Array.from(fileObj.data)], name, {type: fileObj.type}));
});
return retval;
}
/**
{
"rawHeader": "Message-ID: <39235FC5.276CCE00@example.com>\nDate: Wed, 17 May 2000 23:13:09 -0400\nFrom: Doug Sauder <dwsauder@example.com>\nX-Mailer: Mozilla 4.7 [en] (WinNT; I)\nX-Accept-Language: en\nMIME-Version: 1.0\nTo: Heinz =?iso-8859-1?Q?M=FCller?= <mueller@example.com>\nSubject: Die Hasen und die =?iso-8859-1?Q?Fr=F6sche?= (Netscape Messenger 4.7)\nContent-Type: multipart/mixed;\n boundary=\"------------A1E83A41894D3755390B838A\"",
"body": [
{
"rawHeader": "\nContent-Type: multipart/alternative;\n boundary=\"------------F03F94BA73D3B9E8C1B94D92\"",
"body": [
{
"rawHeader": "\nContent-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: quoted-printable",
"body": "[blue ball]\n\nDie Hasen und die Fr=F6sche\n\nDie Hasen klagten einst =FCber ihre mi=DFliche Lage; \"wir leben\", sprach =\nein\nRedner, \"in steter Furcht vor Menschen und Tieren, eine Beute der Hunde,\nder Adler, ja fast aller Raubtiere! Unsere stete Angst ist =E4rger als de=\nr\nTod selbst. Auf, la=DFt uns ein f=FCr allemal sterben.\"\n\nIn einem nahen Teich wollten sie sich nun ers=E4ufen; sie eilten ihm zu;\nallein das au=DFerordentliche Get=F6se und ihre wunderbare Gestalt\nerschreckte eine Menge Fr=F6sche, die am Ufer sa=DFen, so sehr, da=DF sie=\n aufs\nschnellste untertauchten.\n\n\"Halt\", rief nun eben dieser Sprecher, \"wir wollen das Ers=E4ufen noch ei=\nn\nwenig aufschieben, denn auch uns f=FCrchten, wie ihr seht, einige Tiere,\nwelche also wohl noch ungl=FCcklicher sein m=FCssen als wir.\"\n\n[Image]\n\n\n",
"header": {
"content-type": [
"text/plain; charset=iso-8859-1"
],
"content-transfer-encoding": [
"quoted-printable"
]
}
},
{
"rawHeader": "\nContent-Type: multipart/related;\n boundary=\"------------C02FA3D0A04E95F295FB25EB\"",
"body": [
{
"rawHeader": "\nContent-Type: text/html; charset=us-ascii\nContent-Transfer-Encoding: 7bit",
"body": "<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">\n<html>\n<img SRC=\"cid:part1.39235FC5.E71D8178@example.com\" ALT=\"blue ball\" height=27 width=27><b></b>\n<p><b>Die Hasen und die Fr&ouml;sche</b>\n<p>Die Hasen klagten einst &uuml;ber ihre mi&szlig;liche Lage; \"wir leben\",\nsprach ein Redner, \"in steter Furcht vor Menschen und Tieren, eine Beute\nder Hunde, der Adler, ja fast aller Raubtiere! Unsere stete Angst ist &auml;rger\nals der Tod selbst. Auf, la&szlig;t uns ein f&uuml;r allemal sterben.\"\n<p>In einem nahen Teich wollten sie sich nun ers&auml;ufen; sie eilten\nihm zu; allein das au&szlig;erordentliche Get&ouml;se und ihre wunderbare\nGestalt erschreckte eine Menge Fr&ouml;sche, die am Ufer sa&szlig;en, so\nsehr, da&szlig; sie aufs schnellste untertauchten.\n<p>\"Halt\", rief nun eben dieser Sprecher, \"wir wollen das Ers&auml;ufen\nnoch ein wenig aufschieben, denn auch uns f&uuml;rchten, wie ihr seht,\neinige Tiere, welche also wohl noch ungl&uuml;cklicher sein m&uuml;ssen\nals wir.\"\n<p><img SRC=\"cid:part2.39235FC5.E71D8178@example.com\" height=27 width=27>\n<br>&nbsp;\n<br>&nbsp;</html>\n",
"header": {
"content-type": [
"text/html; charset=us-ascii"
],
"content-transfer-encoding": [
"7bit"
]
}
},
{
"rawHeader": "\nContent-Type: image/png\nContent-ID: <part1.39235FC5.E71D8178@example.com>\nContent-Transfer-Encoding: base64\nContent-Disposition: inline; filename=\"C:\\TEMP\\nsmailEG.png\"",
"body": "iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAgAABAAABgA\nAAAACCkAEEIAEEoACDEAEFIIIXMIKXsIKYQIIWsAGFoACDkIIWMQOZwYQqUYQq0YQrUQOaUQ\nMZQAGFIQMYwpUrU5Y8Y5Y84pWs4YSs4YQs4YQr1Ca8Z7nNacvd6Mtd5jlOcxa94hUt4YStYY\nQsYQMaUAACHO5+/n7++cxu9ShO8pWucQOa1Ke86tzt6lzu9ajO8QMZxahNat1ufO7++Mve9K\ne+8YOaUYSsaMvee15++Uve8AAClajOdzpe9rnO8IKYwxY+8pWu8IIXsAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADB\nMg1VAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAGI\nSURBVHicddJtV5swGAbgEk6AJhBSk4bMCUynBSLaqovbrG/bfPn/vyh70lbsscebL5xznTsh\n5BmNhgQoRChwo50EOIohUYLDj4zHhKYQkrEoQdvock4ne0IKMVUpKZLQDeqSTIsv+18PyqqW\nUw2IBsRM7307PPp+fDJrWtnpLDJvewYxnewfnvanZ+fzpmwXijC8KbqEa3Fx2ff91Y95U9XC\nUpaDeQwiMpHXP/v+1++bWVPWQoGFawtjury9vru/f/C1Vi7ezT0WWpQHf/7+u/G71aLThK/M\njRxmT6KdzZ9fGk9yatMsTgZLl3XVgFRAC6spj/13enssqJVtWVa3NdBSacL8+VZmYqKmdd1C\nSYoOiMOSGwtzlqqlFFIuOqv0a1ZEZrUkWICLLFW266y1KvWE1zV/iDAH1EopnVLCiygZCIom\nH3NCKX0lnI+B1iuuzCGTxwXjnDO4d7NpbX42YJJHkBwmAm2TxwAZg40J3+Xtbv1rgOAZwG0N\nxW62p+lT+Yi747sD/wEUVMzYmWkOvwAAACV0RVh0Q29tbWVudABjbGlwMmdpZiB2LjAuNiBi\neSBZdmVzIFBpZ3VldDZzO7wAAAAASUVORK5CYII=",
"header": {
"content-type": [
"image/png"
],
"content-id": [
"<part1.39235FC5.E71D8178@example.com>"
],
"content-transfer-encoding": [
"base64"
],
"content-disposition": [
"inline; filename=\"C:\\TEMP\\nsmailEG.png\""
]
}
},
{
"rawHeader": "\nContent-Type: image/png\nContent-ID: <part2.39235FC5.E71D8178@example.com>\nContent-Transfer-Encoding: base64\nContent-Disposition: inline; filename=\"C:\\TEMP\\nsmail39.png\"",
"body": "iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAABAAALAAAV\nAAAaAAAXAAARAAAKAAADAAAcAAAyAABEAABNAABIAAA9AAAjAAAWAAAmAABhAAB7AACGAACH\nAAB9AAB0AABgAAA5AAAUAAAGAAAnAABLAABvAACQAAClAAC7AAC/AACrAAChAACMAABzAABb\nAAAuAAAIAABMAAB3AACZAAC0GRnKODjVPT3bKSndBQW4AACoAAB5AAAxAAAYAAAEAABFAACa\nAAC7JCTRYWHfhITmf3/mVlbqHx/SAAC5AACjAABdAABCAAAoAAAJAABnAAC6Dw/QVFTek5Pl\nrKzpmZntZWXvJSXXAADBAACxAACcAABtAABTAAA2AAAbAAAFAABKAACBAADLICDdZ2fonJzr\npqbtiorvUVHvFBTRAADDAAC2AAB4AABeAABAAAAiAABXAACSAADCAADaGxvoVVXseHjveHjv\nV1fvJibhAADOAAC3AACnAACVAABHAAArAAAPAACdAADFAADhBQXrKCjvPDzvNTXvGxvjAADQ\nAADJAAC1AACXAACEAABsAABPAAASAAACAABiAADpAADvAgLnAADYAADLAAC6AACwAABwAAAT\nAAAkAABYAADIAADTAADNAACzAACDAABuAAAeAAB+AADAAACkAACNAAB/AABpAABQAAAwAACR\nAACpAAC8AACqAACbAABlAABJAAAqAAAOAAA0AACsAACvAACtAACmAACJAAB6AABrAABaAAA+\nAAApAABqAACCAACfAACeAACWAACPAAB8AAAZAAAHAABVAACOAACKAAA4AAAQAAA/AAByAACA\nAABcAAA3AAAsAABmAABDAABWAAAgAAAzAAA8AAA6AAAfAAAMAAAdAAANAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8\nLtlFAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAII\nSURBVHicY2CAg/8QwIABmJhZWFnZ2Dk4MaU5uLh5eHn5+LkFBDlQJf8zC/EIi4iKiUtI8koJ\nScsgyf5nlpWTV1BUUlZRVVPX4NFk1UJIyghp6+jq6RsYGhmbKJgK85mZW8Dk/rNaSlhZ29ja\n2Ts4Ojkr6Li4urFDNf53N/Ow8vTy9vH18w8IDAoWDQkNC4+ASP5ni4wKio6JjYtPSExKTnFW\nSE1LF4A69n9GZlZ2Tm5efkFhUXFySWlZlEd5RSVY7j+TkGRVdU1tXX1DY1Ozcktpa1t7h2Yn\nOAj+d7l1tyo79vT29SdNSJ44SbFVdHIo9xSIHNPUaWqTpifNSJrZnK00S0U1a/acUG5piNz/\nuXLzVJ2qm6dXz584S2WB1cJFi5cshZr539xVftnyFKUVTi2TVjqvyhJLXb1m7TqoHPt6F/HW\n0g0bN63crGqVtWXrtu07BJihcsw71+zanRW8Z89eq337RQ/Ip60xO3gIElX/LbikDm8T36Kw\nbNmRo7O3zpHkPSZwHBqL//8flz1x2OOkyKJTi7aqbzutfUZI2gIuF8F2lr/D5dw2+fZdwpl8\nYVOlI+CJ4/9/joOyYed5QzMvhGqnm2V0WiClm///D0lfXHtJ6vLlK9w7rx7vQk5SQJbFtSms\n1y9evXid7QZacgOxmSxktNzdtSwwU+J/VICaCPFIYU3XAJhIOtjf5sfyAAAAJXRFWHRDb21t\nZW50AGNsaXAyZ2lmIHYuMC42IGJ5IFl2ZXMgUGlndWV0NnM7vAAAAABJRU5ErkJggg==",
"header": {
"content-type": [
"image/png"
],
"content-id": [
"<part2.39235FC5.E71D8178@example.com>"
],
"content-transfer-encoding": [
"base64"
],
"content-disposition": [
"inline; filename=\"C:\\TEMP\\nsmail39.png\""
]
}
}
],
"header": {
"content-type": [
"multipart/related; boundary=\"------------C02FA3D0A04E95F295FB25EB\""
]
}
}
],
"header": {
"content-type": [
"multipart/alternative; boundary=\"------------F03F94BA73D3B9E8C1B94D92\""
]
}
},
{
"rawHeader": "\nContent-Type: image/png;\n name=\"redball.png\"\nContent-Transfer-Encoding: base64\nContent-Disposition: inline;\n filename=\"redball.png\"",
"body": "iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAABAAALAAAV\nAAAaAAAXAAARAAAKAAADAAAcAAAyAABEAABNAABIAAA9AAAjAAAWAAAmAABhAAB7AACGAACH\nAAB9AAB0AABgAAA5AAAUAAAGAAAnAABLAABvAACQAAClAAC7AAC/AACrAAChAACMAABzAABb\nAAAuAAAIAABMAAB3AACZAAC0GRnKODjVPT3bKSndBQW4AACoAAB5AAAxAAAYAAAEAABFAACa\nAAC7JCTRYWHfhITmf3/mVlbqHx/SAAC5AACjAABdAABCAAAoAAAJAABnAAC6Dw/QVFTek5Pl\nrKzpmZntZWXvJSXXAADBAACxAACcAABtAABTAAA2AAAbAAAFAABKAACBAADLICDdZ2fonJzr\npqbtiorvUVHvFBTRAADDAAC2AAB4AABeAABAAAAiAABXAACSAADCAADaGxvoVVXseHjveHjv\nV1fvJibhAADOAAC3AACnAACVAABHAAArAAAPAACdAADFAADhBQXrKCjvPDzvNTXvGxvjAADQ\nAADJAAC1AACXAACEAABsAABPAAASAAACAABiAADpAADvAgLnAADYAADLAAC6AACwAABwAAAT\nAAAkAABYAADIAADTAADNAACzAACDAABuAAAeAAB+AADAAACkAACNAAB/AABpAABQAAAwAACR\nAACpAAC8AACqAACbAABlAABJAAAqAAAOAAA0AACsAACvAACtAACmAACJAAB6AABrAABaAAA+\nAAApAABqAACCAACfAACeAACWAACPAAB8AAAZAAAHAABVAACOAACKAAA4AAAQAAA/AAByAACA\nAABcAAA3AAAsAABmAABDAABWAAAgAAAzAAA8AAA6AAAfAAAMAAAdAAANAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8\nLtlFAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAII\nSURBVHicY2CAg/8QwIABmJhZWFnZ2Dk4MaU5uLh5eHn5+LkFBDlQJf8zC/EIi4iKiUtI8koJ\nScsgyf5nlpWTV1BUUlZRVVPX4NFk1UJIyghp6+jq6RsYGhmbKJgK85mZW8Dk/rNaSlhZ29ja\n2Ts4Ojkr6Li4urFDNf53N/Ow8vTy9vH18w8IDAoWDQkNC4+ASP5ni4wKio6JjYtPSExKTnFW\nSE1LF4A69n9GZlZ2Tm5efkFhUXFySWlZlEd5RSVY7j+TkGRVdU1tXX1DY1Ozcktpa1t7h2Yn\nOAj+d7l1tyo79vT29SdNSJ44SbFVdHIo9xSIHNPUaWqTpifNSJrZnK00S0U1a/acUG5piNz/\nuXLzVJ2qm6dXz584S2WB1cJFi5cshZr539xVftnyFKUVTi2TVjqvyhJLXb1m7TqoHPt6F/HW\n0g0bN63crGqVtWXrtu07BJihcsw71+zanRW8Z89eq337RQ/Ip60xO3gIElX/LbikDm8T36Kw\nbNmRo7O3zpHkPSZwHBqL//8flz1x2OOkyKJTi7aqbzutfUZI2gIuF8F2lr/D5dw2+fZdwpl8\nYVOlI+CJ4/9/joOyYed5QzMvhGqnm2V0WiClm///D0lfXHtJ6vLlK9w7rx7vQk5SQJbFtSms\n1y9evXid7QZacgOxmSxktNzdtSwwU+J/VICaCPFIYU3XAJhIOtjf5sfyAAAAJXRFWHRDb21t\nZW50AGNsaXAyZ2lmIHYuMC42IGJ5IFl2ZXMgUGlndWV0NnM7vAAAAABJRU5ErkJggg==",
"header": {
"content-type": [
"image/png; name=\"redball.png\""
],
"content-transfer-encoding": [
"base64"
],
"content-disposition": [
"inline; filename=\"redball.png\""
]
}
},
{
"rawHeader": "\nContent-Type: image/png;\n name=\"greenball.png\"\nContent-Transfer-Encoding: base64\nContent-Disposition: inline;\n filename=\"greenball.png\"",
"body": "iVBORw0KGgoAAAANSUhEUgAAABsAAAAbCAMAAAC6CgRnAAADAFBMVEX///8AAAAAEAAAGAAA\nIQAACAAAMQAAQgAAUgAAWgAASgAIYwAIcwAIewAQjAAIawAAOQAAYwAQlAAQnAAhpQAQpQAh\nrQBCvRhjxjFjxjlSxiEpzgAYvQAQrQAYrQAhvQCU1mOt1nuE1lJK3hgh1gAYxgAYtQAAKQBC\nzhDO55Te563G55SU52NS5yEh3gAYzgBS3iGc52vW75y974yE71JC7xCt73ul3nNa7ykh5wAY\n1gAx5wBS7yFr7zlK7xgp5wAp7wAx7wAIhAAQtQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAp\n1fnZAAAAAXRSTlMAQObYZgAAABZ0RVh0U29mdHdhcmUAZ2lmMnBuZyAyLjAuMT1evmgAAAFt\nSURBVHicddJtV8IgFAdwD2zIgMEE1+NcqdsoK+m5tCyz7/+ZiLmHsyzvq53zO/cy+N9ery1b\nVe9PWQA9z4MQ+H8Yoj7GASZ95IHfaBGmLOSchyIgyOu22mgQSjUcDuNYcoGjLiLK1cHh0fHJ\naTKKOcMItgYxT89OzsfjyTTLC8UF0c2ZNmKquJhczq6ub+YmSVUYRF59GeDastu7+9nD41Nm\nkiJ2jc2J3kAWZ9Pr55fH18XSmRuKUTXUaqHy7O19tfr4NFle/w3YDrWRUIlZrL/W86XJkyJV\nG9EaEjIx2XyZmZJGioeUaL+2AY8TY8omR6nkLKhu70zjUKVJXsp3quS2DVSJWNh3zzJKCyex\nI0ZxBP3afE0ElyqOlZJyw8r3BE2SFiJCyxA434SCkg65RhdeQBljQtCg39LWrA90RDDG1EWr\nYUO23hMANUKRRl61E529cR++D2G5LK002dr/qrcfu9u0V3bxn/XdhR/NYeeN0ggsLAAAACV0\nRVh0Q29tbWVudABjbGlwMmdpZiB2LjAuNiBieSBZdmVzIFBpZ3VldDZzO7wAAAAASUVORK5C\nYII=",
"header": {
"content-type": [
"image/png; name=\"greenball.png\""
],
"content-transfer-encoding": [
"base64"
],
"content-disposition": [
"inline; filename=\"greenball.png\""
]
}
}
],
"header": {
"message-id": [
"<39235FC5.276CCE00@example.com>"
],
"date": [
"Wed, 17 May 2000 23:13:09 -0400"
],
"from": [
"Doug Sauder <dwsauder@example.com>"
],
"x-mailer": [
"Mozilla 4.7 [en] (WinNT; I)"
],
"x-accept-language": [
"en"
],
"mime-version": [
"1.0"
],
"to": [
"Heinz Müller <mueller@example.com>"
],
"subject": [
"Die Hasen und die Frösche (Netscape Messenger 4.7)"
],
"content-type": [
"multipart/mixed; boundary=\"------------A1E83A41894D3755390B838A\""
]
}
}
*/
/**
* Simple function to add a common file extention based on mime type string.
*
* @param {string} mimetype
* @returns {string}
*/
static getFileExt(mimetype) {
switch (mimetype) {
case "text/plain":
return ".txt";
case "text/html":
return ".htm";
case "application/rtf":
return ".rtf";
}
return ".bin";
}
/**
* Helper function to return objects as an array.
* Extract data from mimeObjects and return object array containing them.
*
* @param {string[][]} headerObjects
* @param {boolean} header
* @param {boolean} body
* @param {boolean} recursive
* @returns {object[]}
*/
toObjArray() {
const out = [];
Mime.walkMime(this.mimeObj, mimePart => out.push(mimePart));
return out;
extractData(headerObjects, header=true, body=true, recursive=true) {
const output = [];
Mime.walkMime(this.mimeObj, function(mimePart) {
const outObj = {};
outObj.fields = {};
if (body) {
const contType = Mime._extractField(mimePart, "content-type");
if (contType && !contType.startsWith("multipart/")) {
outObj.body = mimePart.body;
} else {
outObj.body = null;
}
}
if (header) {
outObj.header = mimePart.rawHeader;
}
if (!headerObjects) {
output.push(outObj);
return;
}
if (!Array.isArray(headerObjects)) {
throw new OperationError("Invalid extraction in headers. Not an Array.");
}
headerObjects.forEach(function(obj) {
if (!Array.isArray(obj)) {
throw new OperationError("Invalid extraction in headers Object. Not an Array.");
}
switch (obj.length) {
case 2:
outObj.fields[obj[0]] = Mime._extractField(mimePart, obj[1]);
break;
case 3:
outObj.fields[obj[0]] = Mime._extractField(mimePart, obj[1], obj[2]);
break;
default:
throw new OperationError("Invalid extraction in headers. Invalid Array size.");
}
});
output.push(outObj);
}, recursive);
return output;
}
/**
* Walks a MIME document and returns an array of Mime data.
* Common helper function to decode Mime encoded words in headers.
*
* @param {boolean} recursive
*/
decodeHeaderWords(recursive=true) {
Mime.walkMime(this.mimeObj, mimePart => {
if (mimePart.rawHeader) {
mimePart.rawHeader = Mime.replaceEncodedWord(mimePart.rawHeader);
}
}, recursive);
}
/**
* Common helper function to decode Mime bodies.
*
* @param {boolean} recursive
*/
decodeMimeObjects(recursive=true) {
Mime.walkMime(this.mimeObj,
mimePart => Mime.decodeMimeMessage(mimePart),
recursive);
}
/**
* Walks a MIME document and returns a Mime Object.
*
* @param {string} mimeData
* @returns {object}
*/
static _parseMime(mimeData) {
let mimeObj = Mime._splitParseHead(mimeData);
const contType = Mime.decodeComplexField(mimeObj, "content-type");
const boundary = Mime.decodeComplexField(mimeObj, "content-type", "boundary");
if (contType && contType.startsWith("multipart/")) {
const mimeObj = Mime._splitParseHead(mimeData);
const contType = Mime._extractField(mimeObj, "content-type");
const boundary = Mime._extractField(mimeObj, "content-type", "boundary");
if (mimeObj.body && contType && contType.startsWith("multipart/")) {
if (!boundary) {
throw new OperationError("Invalid mulitpart section no boundary");
}
const sections = [];
Mime._splitMultipart(mimeObj.body, boundary).forEach((mimePart) => {
sections.push(Mime._parseMime(mimePart));
}, sections);
mimeObj.body = sections;
}
return mimeObj
return mimeObj;
}
/**
* Executes methods on a mime object. These methods should modify the mimeObj.
* Executes a function on a mime object. These methods should modify the mimeObj.
*
* @param {Object} mimeObj
* @param {function[]} methods
* @param {function} methods
* @param {boolean} recursive
* @returns {null}
*/
static walkMime(mimeObj, methods, recursive=true) {
let contType = Mime.decodeComplexField(mimeObj, "content-type");
if (recursive && contType && contType.startsWith("mulitpart/")) {
mimeObj.body.forEach(obj => Mime.walkMime(obj, methods));
} else {
methods.forEach(method => method(mimeObj));
static walkMime(mimeObj, method, recursive=true) {
const contType = Mime._extractField(mimeObj, "content-type");
method(mimeObj);
if (recursive && mimeObj.body && contType && contType.startsWith("multipart/")) {
mimeObj.body.forEach(obj => Mime.walkMime(obj, method));
}
}
@ -281,19 +143,18 @@ class Mime {
* Attempts to decode a mimeObj's data by applying appropriate character and content decoders based on the header data.
*
* @param {Object} mimeObj
* @returns {null}
*/
static decodeMimeMessage(mimeObj) {
let contType = Mime.decodeComplexField(mimeObj, "content-type"),
charEnc = Mime.decodeComplexField(mimeObj, "content-type", "charset"),
contEnc = Mime.decodeComplexField(mimeObj, "content-transfer-encoding");
const contType = Mime._extractField(mimeObj, "content-type");
const contEnc = Mime._extractField(mimeObj, "content-transfer-encoding");
let charEnc = Mime._extractField(mimeObj, "content-type", "charset");
if (contType != null) {
if (!charEnc && contType.startsWith("text/")) {
charEnc = "us-ascii";
}
}
if (contEnc && typeof mimeObj.body === "string") {
mimeObj.body = Mime.decodeMimeData(mimeObj.body, charEnc, contEnc);
if (mimeObj.body && contEnc && typeof mimeObj.body === "string") {
mimeObj.body = Mime._decodeMimeData(mimeObj.body, charEnc, contEnc);
}
}
@ -332,10 +193,21 @@ class Mime {
const matchObj = emlRegex.exec(input);
if (matchObj) {
const splitEmail = [input.substring(0, matchObj.index), input.substring(emlRegex.lastIndex)];
return {rawHeader: splitEmail[0], body: splitEmail[1], header: Mime._parseHeader(splitEmail[0])};
}
return {rawHeader: input, body: null, header: Mime._parseHeader(input)};
}
/**
*
*
*
*/
static _parseHeader(input) {
const sectionRegex = /([A-Za-z-]+):\s+([\x00-\xff]+?)(?=$|\r?\n\S)/g;
const headerObj = {};
let section;
while ((section = sectionRegex.exec(splitEmail[0]))) {
while ((section = sectionRegex.exec(input))) {
const fieldName = section[1].toLowerCase();
const fieldValue = Mime.replaceEncodedWord(section[2].replace(/\n|\r/g, " "));
if (fieldName in headerObj) {
@ -344,9 +216,7 @@ class Mime {
headerObj[fieldName] = [fieldValue];
}
}
return {rawHeader: splitEmail[0], body: splitEmail[1], header: headerObj};
}
return null;
return headerObj;
}
/**
@ -373,14 +243,14 @@ class Mime {
}
/**
* Parses a complex header field and returns an object that contains
* Parses a header field and returns an object that contains
* normalized keys with corresponding values along with single values under
* a value array.
*
* @param {string} field
* @returns {string}
*/
static decodeComplexField(mimeObj, field, subfield="value") {
static _extractField(mimeObj, field, subfield="value") {
if (mimeObj.header.hasOwnProperty(field)) {
const fieldSplit = mimeObj.header[field][0].split(/;\s+/g);
for (let i = 0; i < fieldSplit.length; i++) {
@ -389,7 +259,10 @@ class Mime {
if (fieldSplit[i].length > eq) {
const kv = [fieldSplit[i].substring(0, eq), fieldSplit[i].substring(eq + 1).trim()];
if ((kv[1].startsWith("'") && kv[1].endsWith("'")) || (kv[1].startsWith("\"") && kv[1].endsWith("\""))) {
kv[1] = (/(['"])(.+)\1/.exec(kv[1]))[2];
const val = (/(['"])(.+)\1/.exec(kv[1]));
if (val && val.length === 3) {
kv[1] = val[2];
}
}
if (subfield.toLowerCase() === kv[0].toLowerCase()) {
return kv[1];
@ -397,7 +270,7 @@ class Mime {
} else {
throw OperationError("Not a valid header entry");
}
} else if (subfield == "value"){
} else if (subfield === "value"){
return fieldSplit[i].trim().toLowerCase();
}
}
@ -405,7 +278,6 @@ class Mime {
return null;
}
//TODO: make this a yield instead of string array.
/**
* Splits a Mime document by the current boundaries and attempts to account
* for the current new line size which can be either the standard \r\n or \n.
@ -418,7 +290,7 @@ class Mime {
const output = [];
const newline = input.indexOf("\r") >= 0 ? "\r\n" : "\n";
const boundaryStr = newline.concat("--", boundary);
const last = input.indexOf(newline.concat("--", boundary, "--"));
const last = input.indexOf(boundaryStr, "--");
let begin = 0;
for (let end = 0; end !== last; begin = end) {
begin = input.indexOf(boundaryStr, begin);

View file

@ -9,7 +9,7 @@ import Mime from "../lib/Mime";
import Utils from "../Utils";
/**
* Operation for parsing IMF messages into file list.
*
*/
class ParseIMF extends Operation {
@ -18,12 +18,11 @@ class ParseIMF extends Operation {
*/
constructor() {
super();
this.name = "Parse Internet Message Format";
this.module = "Default";
this.description = ["Parse an IMF formatted messages following RFC5322.",
"<br><br>",
"Parses an IMF formated message like those sent in SMTP. These often have the file extention &quot;.eml&quote; and contain the email headers and body. The output will be a file list of the root header and decoded mime parts."
"Parses an IMF formated message. These often have the file extention &quot;.eml&quote; and contain the email headers and body. The output will be a file list of the root header and decoded mime parts.",
].join("\n");
this.infoURL = "https://tools.ietf.org/html/rfc5322";
this.inputType = "string";
@ -31,7 +30,7 @@ class ParseIMF extends Operation {
this.presentType = "html";
this.args = [
{
"name": "Decode Mime Encoded Words",
"name": "Decode Encoded-Words",
"type": "boolean",
"value": false
}
@ -39,12 +38,68 @@ class ParseIMF extends Operation {
}
/**
* @param {string}
* @param {Object[]}
* Basic Email Parser that displays the header and mime sections as files.
* Args 0 boolean decode quoted words
*
* @param {string} input
* @param {boolean} decodeWords
* @returns {File[]}
*/
run(input, args) {
return new Mime(input).decodeMime(args[0]);
const eml = new Mime(input);
if (!eml.mimeObj) {
return [];
}
eml.decodeMimeObjects();
if (args[0]) {
eml.decodeHeaderWords(false);
}
const fields = [["filename", "content-disposition", "filename"],
["name", "content-type", "name"],
["type", "content-type"],
["subject", "subject"]];
const dataObj = eml.extractData(fields);
let subject = null;
const retval = [];
if (dataObj.length >= 1) {
subject = dataObj[0].fields.subject;
if (dataObj[0].header) {
retval.push(new File([dataObj[0].header], "Header.txt", {type: "text/plain"}));
}
}
dataObj.forEach(function(obj) {
if (obj.body) {
let name = obj.fields.filename ? obj.fields.filename : obj.fields.name;
const type = obj.fields.type ? obj.fields.type : "text/plain";
if (!name) {
name = (subject ? subject : "Undefined").concat(ParseIMF.getFileExt(type));
}
if (Array.isArray(obj.body)) {
retval.push(new File([Uint8Array.from(obj.body)], name, {type: type}));
} else {
retval.push(new File([obj.body], name, {type: type}));
}
}
});
return retval;
}
/**
* Simple function to add a common file extention based on mime type string.
*
* @param {string} mimetype
* @returns {string}
*/
static getFileExt(mimetype) {
switch (mimetype) {
case "text/plain":
return ".txt";
case "text/html":
return ".htm";
case "application/rtf":
return ".rtf";
}
return ".bin";
}
/**