mime-js

Create MIME message from browser, fork of https://github.com/ikr0m/mime-js
git clone git://git.defalsify.org/mime-js.git
Log | Files | Refs | LICENSE

commit 41431712850be7f10c7bea3dec6b75f76bade976
parent 134b06330e353152148ab314df6f799ae46d2225
Author: Ikrom <ikromur@gmail.com>
Date:   Sun, 10 May 2015 17:02:54 +0500

Mime.toMimeTxt and Mime.toMimeObj methods

Diffstat:
M.gitignore | 3++-
MReadme.md | 62+++++++++++++++++++++++++++++++++++++++++---------------------
Mdemo/index.html | 21+++++++++++++++------
Adist/mime-js.js | 497+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adist/mime-js.min.js | 2++
Msrc/mime-js.coffee | 559+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
6 files changed, 1007 insertions(+), 137 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -3,9 +3,10 @@ project/project project/target target tmp +/.bin +node_modules/ .history .directory -dist /.idea /*.iml /out diff --git a/Readme.md b/Readme.md @@ -9,60 +9,80 @@ Install Run from terminal: `npm install` + `gulp` -Usage ------ +Sample Usage +------------ + +Mime object has two public methods: Mime.toMimeTxt() and Mime.toMimeObj(). See sample and its result. -Call **Mime.toMimeTxt** function with *mail* object: ```javascript -var mail = { - "to": "email1@example.com, email2@example.com", +var originalMail = { + "to": "email1@example.com", "subject": "Today is rainy", "fromName": "John Smith", "from": "john.smith@mail.com", "body": "Sample body text", "cids": [], "attaches" : [] -} -createMimeMessage(mail); +}; +var mimeTxt = Mime.toMimeTxt(originalMail); +var mimeObj = Mime.toMimeObj(mimeTxt); +console.log(mimeTxt); +console.log(mimeObj); + ``` -***Result:*** +**Result** + +MimeTxt +------- ``` MIME-Version: 1.0 -Date: Sat, 18 Oct 2014 10:33:33 +0000 -Delivered-To: email1@example.com -Message-ID: <24jzegg8ghiod2t9ceku9gck746uhaor@mail.your-domain.com> +Date: Sun, 10 May 2015 11:50:39 +0000 +Message-ID: <i2ozrb4lgrgrpb9hp8wrf4n449xjemi@mail.your-domain.com> Subject: =?UTF-8?B?VG9kYXkgaXMgcmFpbnk=?= From: =?UTF-8?B?Sm9obiBTbWl0aA==?= <john.smith@mail.com> To: email1@example.com -Content-Type: multipart/mixed; boundary=ko4nd8p2ef2bj4i29277j78q0azto6r +Content-Type: multipart/mixed; boundary=qr7c8bjwkc81if6r9xpqmra8rrudi ---ko4nd8p2ef2bj4i29277j78q0azto6r -Content-Type: multipart/related; boundary=lwayf4vfgfhcl3dipsq6t2hoaa2rcnmi +--qr7c8bjwkc81if6r9xpqmra8rrudi +Content-Type: multipart/related; boundary=zh0eu0iqfdtv5cdiqigyhqinn1r79zfr ---lwayf4vfgfhcl3dipsq6t2hoaa2rcnmi -Content-Type: multipart/alternative; boundary=6ghtwcxztyidaemiv0gzjnw8un8z1tt9 +--zh0eu0iqfdtv5cdiqigyhqinn1r79zfr +Content-Type: multipart/alternative; boundary=56ksn4vpquissjorg32seupolt4eu3di ---6ghtwcxztyidaemiv0gzjnw8un8z1tt9 +--56ksn4vpquissjorg32seupolt4eu3di Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: base64 U2FtcGxlIGJvZHkgdGV4dA== ---6ghtwcxztyidaemiv0gzjnw8un8z1tt9 +--56ksn4vpquissjorg32seupolt4eu3di Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: base64 PGRpdj5TYW1wbGUgYm9keSB0ZXh0PC9kaXY+ ---6ghtwcxztyidaemiv0gzjnw8un8z1tt9-- ---lwayf4vfgfhcl3dipsq6t2hoaa2rcnmi-- ---ko4nd8p2ef2bj4i29277j78q0azto6r-- +--56ksn4vpquissjorg32seupolt4eu3di-- +--zh0eu0iqfdtv5cdiqigyhqinn1r79zfr-- +--qr7c8bjwkc81if6r9xpqmra8rrudi-- +``` + + +MimeObj +------- + +```javascript +{"html":"<div>Sample body text</div>","text":"Sample body text","attaches":[],"innerMsgs":[],"to":"email1@example.com","from":"John Smith <john.smith@mail.com>","subject":"Today is rainy"} ``` +------------------------------------------------------------------ + **cids** - For inline images **attaches** - any file in base64 format + +------------------------------------------------------------------ diff --git a/demo/index.html b/demo/index.html @@ -7,12 +7,17 @@ <script src="../dist/mime-js.js" type="text/javascript"></script> </head> <body> - <b>Mail obj:</b><pre id="mail"></pre> - <hr> - <div id="mime"></div> + <b>Mail obj:</b> + <pre id="mail"></pre> + <hr/> + <h2>MimeTxt</h2> + <div id="mimeTxt"></div> + <hr/> + <h2>MimeObj</h2> + <div id="mimeObj"></div> <script type="application/javascript"> - var mail = { + var originalMail = { "to": "email1@example.com", "subject": "Today is rainy", "fromName": "John Smith", @@ -21,8 +26,12 @@ "cids": [], "attaches" : [] }; - document.getElementById("mail" ).innerText = JSON.stringify(mail); - document.getElementById("mime" ).innerText = Mime.toMimeTxt(mail); + var mimeTxt = Mime.toMimeTxt(originalMail); + var mimeObj = Mime.toMimeObj(mimeTxt); + + document.getElementById("mail" ).innerText = JSON.stringify(originalMail); + document.getElementById("mimeTxt").innerText = mimeTxt; + document.getElementById("mimeObj").innerText = JSON.stringify(mimeObj); </script> </body> </html> diff --git a/dist/mime-js.js b/dist/mime-js.js @@ -0,0 +1,497 @@ + +/* + mime-js.js 0.2.0 + 2014-10-18 + + By Ikrom, https://github.com/ikr0m + License: X11/MIT + */ + +(function() { + window.Mime = (function() { + var MailParser, _util, buildMimeObj, toMimeObj, toMimeTxt; + toMimeTxt = function(mail) { + var alternative, attaches, cids, createAlternative, createAttaches, createCids, createHtml, createMixed, createPlain, createRelated, getBoundary, htm, linkify, plain, related, result; + linkify = function(inputText) { + var replacePattern1, replacePattern2, replacePattern3, replacedText; + replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; + replacedText = inputText.replace(replacePattern1, "<a href=\"$1\" target=\"_blank\">$1</a>"); + replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; + replacedText = replacedText.replace(replacePattern2, "$1<a href=\"http://$2\" target=\"_blank\">$2</a>"); + replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; + replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>'); + return replacedText; + }; + getBoundary = function() { + var _random; + _random = function() { + return Math.random().toString(36).slice(2); + }; + return _random() + _random(); + }; + createPlain = function(textContent) { + if (textContent == null) { + textContent = ''; + } + return '\nContent-Type: text/plain; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + '\n\n' + (Base64.encode(textContent, true)).replace(/.{76}/g, "$&\n"); + }; + createHtml = function(msg) { + var htmlContent; + htmlContent = msg.body || ""; + htmlContent = htmlContent.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/, '&gt;').replace(/\n/g, '\n<br/>'); + htmlContent = linkify(htmlContent); + htmlContent = '<div>' + htmlContent + '</div>'; + return '\nContent-Type: text/html; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + '\n\n' + (Base64.encode(htmlContent, true)).replace(/.{76}/g, "$&\n"); + }; + createAlternative = function(text, html) { + var boundary; + boundary = getBoundary(); + return '\nContent-Type: multipart/alternative; boundary=' + boundary + '\n\n--' + boundary + text + '\n\n--' + boundary + html + '\n\n--' + boundary + '--'; + }; + createCids = function(cids) { + var base64, cid, cidArr, id, j, len, name, type; + if (!cids) { + return; + } + cidArr = []; + for (j = 0, len = cids.length; j < len; j++) { + cid = cids[j]; + type = cid.type; + name = cid.name; + base64 = cid.base64; + id = getBoundary(); + cidArr.push('\nContent-Type: ' + type + '; name=\"' + name + '\"' + '\nContent-Transfer-Encoding: base64' + '\nContent-ID: <' + id + '>' + '\nX-Attachment-Id: ' + id + '\n\n' + base64); + } + return cidArr; + }; + createRelated = function(alternative, cids) { + var boundary, cid, j, len, relatedStr; + if (cids == null) { + cids = []; + } + boundary = getBoundary(); + relatedStr = '\nContent-Type: multipart/related; boundary=' + boundary + '\n\n--' + boundary + alternative; + for (j = 0, len = cids.length; j < len; j++) { + cid = cids[j]; + relatedStr += '\n--' + boundary + cid; + } + return relatedStr + '\n--' + boundary + '--'; + }; + createAttaches = function(attaches) { + var attach, base64, id, j, len, name, result, type; + if (!attaches) { + return; + } + result = []; + for (j = 0, len = attaches.length; j < len; j++) { + attach = attaches[j]; + type = attach.type; + name = attach.name; + base64 = attach.base64; + id = getBoundary(); + result.push('\nContent-Type: ' + type + '; name=\"' + name + '\"' + '\nContent-Disposition: attachment; filename=\"' + name + '\"' + '\nContent-Transfer-Encoding: base64' + '\nX-Attachment-Id: ' + id + '\n\n' + base64); + } + return result; + }; + createMixed = function(related, attaches) { + var attach, boundary, date, j, len, mailFromName, mimeStr, subject; + boundary = getBoundary(); + subject = ''; + if (mail.subject) { + subject = '=?UTF-8?B?' + Base64.encode(mail.subject, true) + '?='; + } + mailFromName = '=?UTF-8?B?' + Base64.encode(mail.fromName || "", true) + '?='; + date = (new Date().toGMTString()).replace(/GMT|UTC/gi, '+0000'); + mimeStr = 'MIME-Version: 1.0' + '\nDate: ' + date + '\nMessage-ID: <' + getBoundary() + '@mail.your-domain.com>' + '\nSubject: ' + subject + '\nFrom: ' + mailFromName + ' <' + mail.from + '>' + '\nTo: ' + mail.to + '\nContent-Type: multipart/mixed; boundary=' + boundary + '\n\n--' + boundary + related; + for (j = 0, len = attaches.length; j < len; j++) { + attach = attaches[j]; + mimeStr += '\n--' + boundary + attach; + } + return (mimeStr + '\n--' + boundary + '--').replace(/\n/g, '\r\n'); + }; + plain = createPlain(mail.body); + htm = createHtml(mail); + alternative = createAlternative(plain, htm); + cids = createCids(mail.cids); + related = createRelated(alternative, cids); + attaches = createAttaches(mail.attaches); + result = createMixed(related, attaches); + return result; + }; + MailParser = function(rawMessage) { + var explodeMessage, from, getValidStr, messageParts, rawHeaders, subject, to; + explodeMessage = function(inMessage) { + var escBoundary, i, inBody, inBodyParts, inBoundary, inContentType, inContentTypeParts, inHeaderPos, inRawBody, inRawHeaders, match, mimeType, mimeTypeParts, regContentType, regString, specialChars; + inHeaderPos = inMessage.indexOf("\r\n\r\n"); + if (inHeaderPos === -1) { + inMessage = inMessage.replace(/\n/g, "\r\n"); + inHeaderPos = inMessage.indexOf("\r\n\r\n"); + if (inHeaderPos === -1) { + inHeaderPos = inMessage.length; + } + } + inRawHeaders = inMessage.slice(0, inHeaderPos).replace(/\r\n\s+/g, " ") + "\r\n"; + inRawBody = inMessage.slice(inHeaderPos).replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, ""); + inContentType = ""; + regContentType = inRawHeaders.match(/Content-Type: (.*)/i); + if (regContentType && regContentType.length > 0) { + inContentType = regContentType[1]; + } else { + console.log("Warning: MailParser: Content-type doesn't exist!"); + } + inContentTypeParts = inContentType.split(";"); + mimeType = inContentTypeParts[0].replace(/\s/g, ""); + mimeTypeParts = mimeType.split("/"); + if (mimeTypeParts[0].toLowerCase() === "multipart") { + inBodyParts = []; + match = inContentTypeParts[1].match(/boundary="?([^"]*)"?/i); + if (!match && inContentTypeParts[2]) { + match = inContentTypeParts[2].match(/boundary="?([^"]*)"?/i); + } + inBoundary = _util.trim(match[1]).replace(/"/g, ""); + escBoundary = inBoundary.replace(/\+/g, "\\+"); + regString = new RegExp("--" + escBoundary, "g"); + inBodyParts = inRawBody.replace(regString, inBoundary).replace(regString, inBoundary).split(inBoundary); + inBodyParts.shift(); + inBodyParts.pop(); + i = 0; + while (i < inBodyParts.length) { + inBodyParts[i] = inBodyParts[i].replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, ""); + inBodyParts[i] = explodeMessage(inBodyParts[i]); + i++; + } + } else { + inBody = inRawBody; + if (mimeTypeParts[0] === "text") { + inBody = inBody.replace(RegExp("=\\r\\n", "g"), ""); + specialChars = inBody.match(RegExp("=[A-F0-9][A-F0-9]", "g")); + if (specialChars) { + i = 0; + while (i < specialChars.length) { + inBody = inBody.replace(specialChars[i], String.fromCharCode(parseInt(specialChars[i].replace(RegExp("="), ""), 16))); + i++; + } + } + } + } + return { + rawHeaders: inRawHeaders, + rawBody: inRawBody, + body: inBody, + contentType: inContentType, + contentTypeParts: inContentTypeParts, + boundary: inBoundary, + bodyParts: inBodyParts, + mimeType: mimeType, + mimeTypeParts: mimeTypeParts + }; + }; + messageParts = ""; + try { + messageParts = explodeMessage(rawMessage); + } catch (_error) {} + rawHeaders = messageParts.rawHeaders; + getValidStr = function(arr) { + if (arr == null) { + arr = []; + } + return arr[1] || ""; + }; + subject = getValidStr(/\r\nSubject: (.*)\r\n/g.exec(rawHeaders)); + to = getValidStr(/\r\nTo: (.*)\r\n/g.exec(rawHeaders)); + from = getValidStr(/\r\nFrom: (.*)\r\n/g.exec(rawHeaders)); + return { + messageParts: messageParts, + subject: subject, + to: to, + from: from + }; + }; + _util = (function() { + var KOIRDec, QPDec, _decodeMimeWord, decode, decodeMimeWords, toHtmlEntity, trim, win1251Dec; + trim = function(str) { + if (str == null) { + str = ''; + } + return (typeof str.trim === "function" ? str.trim() : void 0) || str.replace(/^\s+|\s+$/g, ''); + }; + decode = function(txt, charset) { + var result; + if (txt == null) { + txt = ''; + } + if (charset == null) { + charset = ''; + } + charset = charset.toLowerCase(); + result = (function() { + switch (false) { + case charset.indexOf('koi8-r') === -1: + return KOIRDec(txt); + case charset.indexOf('utf-8') === -1: + return Base64._utf8_decode(txt); + case charset.indexOf('windows-1251') === -1: + return win1251Dec(txt); + default: + return txt; + } + })(); + return result; + }; + QPDec = function(s) { + return s.replace(/\=[\r\n]+/g, "").replace(/\=[0-9A-F]{2}/gi, function(v) { + return String.fromCharCode(parseInt(v.substr(1), 16)); + }); + }; + KOIRDec = function(str) { + var charmap, code2char, i, j, len, res, val; + charmap = unescape("%u2500%u2502%u250C%u2510%u2514%u2518%u251C%u2524%u252C%u2534%u253C%u2580%u2584%u2588%u258C%u2590" + "%u2591%u2592%u2593%u2320%u25A0%u2219%u221A%u2248%u2264%u2265%u00A0%u2321%u00B0%u00B2%u00B7%u00F7" + "%u2550%u2551%u2552%u0451%u2553%u2554%u2555%u2556%u2557%u2558%u2559%u255A%u255B%u255C%u255D%u255E" + "%u255F%u2560%u2561%u0401%u2562%u2563%u2564%u2565%u2566%u2567%u2568%u2569%u256A%u256B%u256C%u00A9" + "%u044E%u0430%u0431%u0446%u0434%u0435%u0444%u0433%u0445%u0438%u0439%u043A%u043B%u043C%u043D%u043E" + "%u043F%u044F%u0440%u0441%u0442%u0443%u0436%u0432%u044C%u044B%u0437%u0448%u044D%u0449%u0447%u044A" + "%u042E%u0410%u0411%u0426%u0414%u0415%u0424%u0413%u0425%u0418%u0419%u041A%u041B%u041C%u041D%u041E" + "%u041F%u042F%u0420%u0421%u0422%u0423%u0416%u0412%u042C%u042B%u0417%u0428%u042D%u0429%u0427%u042A"); + code2char = function(code) { + if (code >= 0x80 && code <= 0xFF) { + return charmap.charAt(code - 0x80); + } + return String.fromCharCode(code); + }; + res = ""; + for (i = j = 0, len = str.length; j < len; i = ++j) { + val = str[i]; + res = res + code2char(str.charCodeAt(i)); + } + return res; + }; + win1251Dec = function(str) { + var i, iCode, j, len, oCode, result, s; + if (str == null) { + str = ''; + } + result = ''; + for (i = j = 0, len = str.length; j < len; i = ++j) { + s = str[i]; + iCode = str.charCodeAt(i); + oCode = (function() { + switch (false) { + case iCode !== 168: + return 1025; + case iCode !== 184: + return 1105; + case !((191 < iCode && iCode < 256)): + return iCode + 848; + default: + return iCode; + } + })(); + result = result + String.fromCharCode(oCode); + } + return result; + }; + _decodeMimeWord = function(str, toCharset) { + var encoding, fromCharset, match; + str = _util.trim(str); + fromCharset = void 0; + encoding = void 0; + match = void 0; + match = str.match(/^\=\?([\w_\-]+)\?([QqBb])\?([^\?]*)\?\=$/i); + if (!match) { + return decode(str, toCharset); + } + fromCharset = match[1]; + encoding = (match[2] || "Q").toString().toUpperCase(); + str = (match[3] || "").replace(/_/g, " "); + if (encoding === "B") { + return Base64.decode(str, toCharset); + } else if (encoding === "Q") { + return QPDec(str); + } else { + return str; + } + }; + decodeMimeWords = function(str, toCharset) { + str = (str || "").toString().replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, "$1").replace(/\=\?([\w_\-]+)\?([QqBb])\?[^\?]*\?\=/g, (function(mimeWord, charset, encoding) { + return _decodeMimeWord(mimeWord); + }).bind(this)); + return decode(str, toCharset); + }; + toHtmlEntity = function(txt) { + if (txt == null) { + txt = ""; + } + return (txt + "").replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); + }; + return { + decode: decode, + KOIRDec: KOIRDec, + win1251Dec: win1251Dec, + decodeMimeWords: decodeMimeWords, + toHtmlEntity: toHtmlEntity, + trim: trim + }; + })(); + buildMimeObj = function(rawMailObj) { + var body, decodeBody, err, isHtml, isText, mergeInnerMsgs, mimeType, parseBodyParts, parts, readyMail, result, wrapPreTag; + readyMail = { + html: "", + text: "", + attaches: [], + innerMsgs: [], + to: _util.decodeMimeWords(rawMailObj.to), + from: _util.decodeMimeWords(rawMailObj.from), + subject: _util.decodeMimeWords(rawMailObj.subject) + }; + decodeBody = function(body, rawHeaders) { + var decBody, isBase64, isQP; + isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders); + isBase64 = /Content-Transfer-Encoding: base64/i.test(rawHeaders); + if (isBase64) { + body = body.replace(/\s/g, ''); + decBody = typeof atob === "function" ? atob(body) : void 0; + if (decBody == null) { + decBody = Base64.decode(body); + } + body = decBody; + } else if (isQP) { + body = _util.QPDec(body); + } + return body; + }; + parseBodyParts = function(bodyParts) { + var attach, body, innerMsg, isAttach, isAudio, isHtml, isImg, isPlain, isQP, j, k, len, len1, mimeType, name, newMimeMsg, part, rawHeaders, ref, ref1, ref2, regex, slashPos, type, typePart; + if (!bodyParts) { + return; + } + for (j = 0, len = bodyParts.length; j < len; j++) { + part = bodyParts[j]; + mimeType = ((ref = part.mimeType) != null ? ref : "").toLowerCase(); + if (mimeType.indexOf('multipart') !== -1) { + parseBodyParts(part.bodyParts); + continue; + } + if (mimeType.indexOf('message/rfc822') !== -1) { + newMimeMsg = MailParser(part.rawBody); + innerMsg = toMimeObj(newMimeMsg); + readyMail.innerMsgs.push(innerMsg); + continue; + } + rawHeaders = part.rawHeaders; + isAttach = rawHeaders.indexOf('Content-Disposition: attachment') !== -1; + body = part.rawBody; + isHtml = /text\/html/.test(mimeType); + isPlain = /text\/plain/.test(mimeType); + isImg = /image/.test(mimeType); + isAudio = /audio/.test(mimeType); + if (isAttach || isImg || isAudio) { + isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders); + if (isQP) { + body = _util.QPDec(body); + body = btoa ? btoa(body) : Base64.encode(body); + } + ref1 = part.contentTypeParts; + for (k = 0, len1 = ref1.length; k < len1; k++) { + typePart = ref1[k]; + if (/name=/i.test(typePart)) { + name = typePart.replace(/(.*)=/, '').replace(/"|'/g, ''); + break; + } + } + if (!name) { + name = isImg ? "image" : isAudio ? "audio" : "attachment"; + name += "_" + Math.floor(Math.random() * 100); + slashPos = mimeType.indexOf('/'); + type = mimeType.substring(slashPos + 1); + if (type.length < 4) { + name += "." + type; + } + } + regex = /(.*)content-id:(.*)<(.*)>/i; + attach = { + type: mimeType, + base64: body, + name: name, + cid: (ref2 = regex.exec(rawHeaders)) != null ? ref2[3] : void 0, + visible: /png|jpeg|jpg|gif/.test(mimeType) + }; + readyMail.attaches.push(attach); + } else if (isHtml || isPlain) { + body = decodeBody(body, rawHeaders); + body = _util.decode(body, part.contentType); + if (isHtml) { + readyMail.html += body; + } + if (isPlain) { + readyMail.text += body; + } + } else { + console.log("Unknown mime type: " + mimeType); + } + } + return null; + }; + try { + parts = rawMailObj.messageParts; + if (!parts) { + return readyMail; + } + mimeType = (parts.mimeType || "").toLowerCase(); + isText = /text\/plain/.test(mimeType); + isHtml = /text\/html/.test(mimeType); + if (mimeType.indexOf('multipart') !== -1) { + parseBodyParts(parts.bodyParts); + } else if (isText || isHtml) { + body = decodeBody(parts.body, parts.rawHeaders); + body = _util.decode(body, parts.contentType); + if (isHtml) { + readyMail.html = body; + } + if (isText) { + readyMail.text = body; + } + } else { + console.log("Warning: mime type isn't supported! mime=" + mimeType); + } + } catch (_error) { + err = _error; + throw new Error(err); + } + wrapPreTag = function(txt) { + return "<pre>" + _util.toHtmlEntity(txt) + "</pre>"; + }; + mergeInnerMsgs = function(mail) { + var htm, innerMsg, innerMsgs, j, len, msg, ref, txt; + innerMsgs = mail.innerMsgs; + if (innerMsgs != null ? innerMsgs.length : void 0) { + if (!_util.trim(mail.html) && mail.text) { + mail.html += wrapPreTag(mail.text); + } + for (j = 0, len = innerMsgs.length; j < len; j++) { + innerMsg = innerMsgs[j]; + msg = mergeInnerMsgs(innerMsg); + txt = msg.text; + htm = msg.html; + if (htm) { + mail.html += htm; + } else if (txt) { + mail.html += wrapPerTag(txt); + mail.text += txt; + } + if (((ref = msg.attaches) != null ? ref.length : void 0) > 0) { + mail.attaches = mail.attaches.concat(msg.attaches); + } + } + } + return mail; + }; + result = mergeInnerMsgs(readyMail); + return result; + }; + toMimeObj = function(mimeMsgText) { + var mailObj, rawMailObj; + rawMailObj = MailParser(mimeMsgText); + mailObj = buildMimeObj(rawMailObj); + return mailObj; + }; + return { + toMimeTxt: toMimeTxt, + toMimeObj: toMimeObj + }; + })(); + +}).call(this); diff --git a/dist/mime-js.min.js b/dist/mime-js.min.js @@ -0,0 +1 @@ +(function(){window.Mime=function(){var e,t,n,r,u;return u=function(e){var t,n,r,u,a,o,i,c,s,l,p,d,f,m,g,h;return f=function(e){var t,n,r,u;return t=/(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim,u=e.replace(t,'<a href="$1" target="_blank">$1</a>'),n=/(^|[^\/])(www\.[\S]+(\b|$))/gim,u=u.replace(n,'$1<a href="http://$2" target="_blank">$2</a>'),r=/(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim,u=u.replace(r,'<a href="mailto:$1">$1</a>')},p=function(){var e;return e=function(){return Math.random().toString(36).slice(2)},e()+e()},s=function(e){return null==e&&(e=""),"\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n"+Base64.encode(e,!0).replace(/.{76}/g,"$&\n")},i=function(e){var t;return t=e.body||"",t=t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/,"&gt;").replace(/\n/g,"\n<br/>"),t=f(t),t="<div>"+t+"</div>","\nContent-Type: text/html; charset=UTF-8\nContent-Transfer-Encoding: base64\n\n"+Base64.encode(t,!0).replace(/.{76}/g,"$&\n")},u=function(e,t){var n;return n=p(),"\nContent-Type: multipart/alternative; boundary="+n+"\n\n--"+n+e+"\n\n--"+n+t+"\n\n--"+n+"--"},o=function(e){var t,n,r,u,a,o,i,c;if(e){for(r=[],a=0,o=e.length;o>a;a++)n=e[a],c=n.type,i=n.name,t=n.base64,u=p(),r.push("\nContent-Type: "+c+'; name="'+i+'"\nContent-Transfer-Encoding: base64\nContent-ID: <'+u+">\nX-Attachment-Id: "+u+"\n\n"+t);return r}},l=function(e,t){var n,r,u,a,o;for(null==t&&(t=[]),n=p(),o="\nContent-Type: multipart/related; boundary="+n+"\n\n--"+n+e,u=0,a=t.length;a>u;u++)r=t[u],o+="\n--"+n+r;return o+"\n--"+n+"--"},a=function(e){var t,n,r,u,a,o,i,c;if(e){for(i=[],u=0,a=e.length;a>u;u++)t=e[u],c=t.type,o=t.name,n=t.base64,r=p(),i.push("\nContent-Type: "+c+'; name="'+o+'"\nContent-Disposition: attachment; filename="'+o+'"\nContent-Transfer-Encoding: base64\nX-Attachment-Id: '+r+"\n\n"+n);return i}},c=function(t,n){var r,u,a,o,i,c,s,l;for(u=p(),l="",e.subject&&(l="=?UTF-8?B?"+Base64.encode(e.subject,!0)+"?="),c="=?UTF-8?B?"+Base64.encode(e.fromName||"",!0)+"?=",a=(new Date).toGMTString().replace(/GMT|UTC/gi,"+0000"),s="MIME-Version: 1.0\nDate: "+a+"\nMessage-ID: <"+p()+"@mail.your-domain.com>\nSubject: "+l+"\nFrom: "+c+" <"+e.from+">\nTo: "+e.to+"\nContent-Type: multipart/mixed; boundary="+u+"\n\n--"+u+t,o=0,i=n.length;i>o;o++)r=n[o],s+="\n--"+u+r;return(s+"\n--"+u+"--").replace(/\n/g,"\r\n")},m=s(e.body),d=i(e),t=u(m,d),r=o(e.cids),g=l(t,r),n=a(e.attaches),h=c(g,n)},e=function(e){var n,r,u,a,o,i,c;n=function(e){var r,u,a,o,i,c,s,l,p,d,f,m,g,h,b,y;if(l=e.indexOf("\r\n\r\n"),-1===l&&(e=e.replace(/\n/g,"\r\n"),l=e.indexOf("\r\n\r\n"),-1===l&&(l=e.length)),d=e.slice(0,l).replace(/\r\n\s+/g," ")+"\r\n",p=e.slice(l).replace(/(\r\n)+$/,"").replace(/^(\r\n)+/,""),c="",h=d.match(/Content-Type: (.*)/i),h&&h.length>0?c=h[1]:console.log("Warning: MailParser: Content-type doesn't exist!"),s=c.split(";"),m=s[0].replace(/\s/g,""),g=m.split("/"),"multipart"===g[0].toLowerCase())for(o=[],f=s[1].match(/boundary="?([^"]*)"?/i),!f&&s[2]&&(f=s[2].match(/boundary="?([^"]*)"?/i)),i=t.trim(f[1]).replace(/"/g,""),r=i.replace(/\+/g,"\\+"),b=new RegExp("--"+r,"g"),o=p.replace(b,i).replace(b,i).split(i),o.shift(),o.pop(),u=0;u<o.length;)o[u]=o[u].replace(/(\r\n)+$/,"").replace(/^(\r\n)+/,""),o[u]=n(o[u]),u++;else if(a=p,"text"===g[0]&&(a=a.replace(RegExp("=\\r\\n","g"),""),y=a.match(RegExp("=[A-F0-9][A-F0-9]","g"))))for(u=0;u<y.length;)a=a.replace(y[u],String.fromCharCode(parseInt(y[u].replace(RegExp("="),""),16))),u++;return{rawHeaders:d,rawBody:p,body:a,contentType:c,contentTypeParts:s,boundary:i,bodyParts:o,mimeType:m,mimeTypeParts:g}},a="";try{a=n(e)}catch(s){}return o=a.rawHeaders,u=function(e){return null==e&&(e=[]),e[1]||""},i=u(/\r\nSubject: (.*)\r\n/g.exec(o)),c=u(/\r\nTo: (.*)\r\n/g.exec(o)),r=u(/\r\nFrom: (.*)\r\n/g.exec(o)),{messageParts:a,subject:i,to:c,from:r}},t=function(){var e,n,r,u,a,o,i,c;return i=function(e){return null==e&&(e=""),("function"==typeof e.trim?e.trim():void 0)||e.replace(/^\s+|\s+$/g,"")},u=function(t,n){var r;return null==t&&(t=""),null==n&&(n=""),n=n.toLowerCase(),r=function(){switch(!1){case-1===n.indexOf("koi8-r"):return e(t);case-1===n.indexOf("utf-8"):return Base64._utf8_decode(t);case-1===n.indexOf("windows-1251"):return c(t);default:return t}}()},n=function(e){return e.replace(/\=[\r\n]+/g,"").replace(/\=[0-9A-F]{2}/gi,function(e){return String.fromCharCode(parseInt(e.substr(1),16))})},e=function(e){var t,n,r,u,a,o,i;for(t=unescape("%u2500%u2502%u250C%u2510%u2514%u2518%u251C%u2524%u252C%u2534%u253C%u2580%u2584%u2588%u258C%u2590%u2591%u2592%u2593%u2320%u25A0%u2219%u221A%u2248%u2264%u2265%u00A0%u2321%u00B0%u00B2%u00B7%u00F7%u2550%u2551%u2552%u0451%u2553%u2554%u2555%u2556%u2557%u2558%u2559%u255A%u255B%u255C%u255D%u255E%u255F%u2560%u2561%u0401%u2562%u2563%u2564%u2565%u2566%u2567%u2568%u2569%u256A%u256B%u256C%u00A9%u044E%u0430%u0431%u0446%u0434%u0435%u0444%u0433%u0445%u0438%u0439%u043A%u043B%u043C%u043D%u043E%u043F%u044F%u0440%u0441%u0442%u0443%u0436%u0432%u044C%u044B%u0437%u0448%u044D%u0449%u0447%u044A%u042E%u0410%u0411%u0426%u0414%u0415%u0424%u0413%u0425%u0418%u0419%u041A%u041B%u041C%u041D%u041E%u041F%u042F%u0420%u0421%u0422%u0423%u0416%u0412%u042C%u042B%u0417%u0428%u042D%u0429%u0427%u042A"),n=function(e){return e>=128&&255>=e?t.charAt(e-128):String.fromCharCode(e)},o="",r=u=0,a=e.length;a>u;r=++u)i=e[r],o+=n(e.charCodeAt(r));return o},c=function(e){var t,n,r,u,a,o,i;for(null==e&&(e=""),o="",t=r=0,u=e.length;u>r;t=++r)i=e[t],n=e.charCodeAt(t),a=function(){switch(!1){case 168!==n:return 1025;case 184!==n:return 1105;case!(n>191&&256>n):return n+848;default:return n}}(),o+=String.fromCharCode(a);return o},r=function(e,r){var a,o,i;return e=t.trim(e),o=void 0,a=void 0,i=void 0,(i=e.match(/^\=\?([\w_\-]+)\?([QqBb])\?([^\?]*)\?\=$/i))?(o=i[1],a=(i[2]||"Q").toString().toUpperCase(),e=(i[3]||"").replace(/_/g," "),"B"===a?Base64.decode(e,r):"Q"===a?n(e):e):u(e,r)},a=function(e,t){return e=(e||"").toString().replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g,"$1").replace(/\=\?([\w_\-]+)\?([QqBb])\?[^\?]*\?\=/g,function(e,t,n){return r(e)}.bind(this)),u(e,t)},o=function(e){return null==e&&(e=""),(e+"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")},{decode:u,KOIRDec:e,win1251Dec:c,decodeMimeWords:a,toHtmlEntity:o,trim:i}}(),n=function(n){var u,a,o,i,c,s,l,p,d,f,m,g;f={html:"",text:"",attaches:[],innerMsgs:[],to:t.decodeMimeWords(n.to),from:t.decodeMimeWords(n.from),subject:t.decodeMimeWords(n.subject)},a=function(e,n){var r,u,a;return a=/Content-Transfer-Encoding: quoted-printable/i.test(n),u=/Content-Transfer-Encoding: base64/i.test(n),u?(e=e.replace(/\s/g,""),r="function"==typeof atob?atob(e):void 0,null==r&&(r=Base64.decode(e)),e=r):a&&(e=t.QPDec(e)),e},p=function(n){var u,o,i,c,s,l,d,m,g,h,b,y,C,x,T,v,w,B,A,E,M,D,F,$,O;if(n){for(h=0,y=n.length;y>h;h++)if(w=n[h],x=(null!=(A=w.mimeType)?A:"").toLowerCase(),-1===x.indexOf("multipart"))if(-1===x.indexOf("message/rfc822"))if(B=w.rawHeaders,c=-1!==B.indexOf("Content-Disposition: attachment"),o=w.rawBody,l=/text\/html/.test(x),m=/text\/plain/.test(x),d=/image/.test(x),s=/audio/.test(x),c||d||s){for(g=/Content-Transfer-Encoding: quoted-printable/i.test(B),g&&(o=t.QPDec(o),o=btoa?btoa(o):Base64.encode(o)),E=w.contentTypeParts,b=0,C=E.length;C>b;b++)if(O=E[b],/name=/i.test(O)){T=O.replace(/(.*)=/,"").replace(/"|'/g,"");break}T||(T=d?"image":s?"audio":"attachment",T+="_"+Math.floor(100*Math.random()),F=x.indexOf("/"),$=x.substring(F+1),$.length<4&&(T+="."+$)),D=/(.*)content-id:(.*)<(.*)>/i,u={type:x,base64:o,name:T,cid:null!=(M=D.exec(B))?M[3]:void 0,visible:/png|jpeg|jpg|gif/.test(x)},f.attaches.push(u)}else l||m?(o=a(o,B),o=t.decode(o,w.contentType),l&&(f.html+=o),m&&(f.text+=o)):console.log("Unknown mime type: "+x);else v=e(w.rawBody),i=r(v),f.innerMsgs.push(i);else p(w.bodyParts);return null}};try{if(d=n.messageParts,!d)return f;l=(d.mimeType||"").toLowerCase(),c=/text\/plain/.test(l),i=/text\/html/.test(l),-1!==l.indexOf("multipart")?p(d.bodyParts):c||i?(u=a(d.body,d.rawHeaders),u=t.decode(u,d.contentType),i&&(f.html=u),c&&(f.text=u)):console.log("Warning: mime type isn't supported! mime="+l)}catch(h){throw o=h,new Error(o)}return g=function(e){return"<pre>"+t.toHtmlEntity(e)+"</pre>"},s=function(e){var n,r,u,a,o,i,c,l;if(u=e.innerMsgs,null!=u?u.length:void 0)for(!t.trim(e.html)&&e.text&&(e.html+=g(e.text)),a=0,o=u.length;o>a;a++)r=u[a],i=s(r),l=i.text,n=i.html,n?e.html+=n:l&&(e.html+=wrapPerTag(l),e.text+=l),(null!=(c=i.attaches)?c.length:void 0)>0&&(e.attaches=e.attaches.concat(i.attaches));return e},m=s(f)},r=function(t){var r,u;return u=e(t),r=n(u)},{toMimeTxt:u,toMimeObj:r}}()}).call(this); +\ No newline at end of file diff --git a/src/mime-js.coffee b/src/mime-js.coffee @@ -1,5 +1,5 @@ ### - mime-js.js 0.1 + mime-js.js 0.2.0 2014-10-18 By Ikrom, https://github.com/ikr0m @@ -7,119 +7,132 @@ ### window.Mime = do -> - linkify = (inputText) -> - #URLs starting with http://, https://, or ftp:// - replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim - replacedText = inputText.replace(replacePattern1, - "<a href=\"$1\" target=\"_blank\">$1</a>") - - #URLs starting with "www." (without // before it, or it'd re-link the ones done above). - replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim - replacedText = replacedText.replace(replacePattern2, - "$1<a href=\"http://$2\" target=\"_blank\">$2</a>") - - replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim - replacedText = replacedText.replace(replacePattern3, - '<a href="mailto:$1">$1</a>') - - replacedText - - getBoundary = -> - _random = -> - Math.random().toString(36).slice(2) - _random() + _random() - - createPlain = (textContent = '') -> - '\nContent-Type: text/plain; charset=UTF-8' + - '\nContent-Transfer-Encoding: base64' + - '\n\n' + (Base64.encode textContent, true).replace(/.{76}/g, "$&\n") - - createHtml = (msg) -> - htmlContent = msg.body || "" - htmlContent = htmlContent.replace(/&/g, '&amp;').replace(/</g, '&lt;') - .replace(/>/, '&gt;').replace(/\n/g, '\n<br/>') - htmlContent = linkify(htmlContent) + # ********************************* + # Create Mime Text from Mail Object + +# var mail = { +# "to": "email1@example.com, email2@example.com", +# "subject": "Today is rainy", +# "fromName": "John Smith", +# "from": "john.smith@mail.com", +# "body": "Sample body text", +# "cids": [], +# "attaches" : [] +# } + toMimeTxt = (mail) -> + linkify = (inputText) -> + #URLs starting with http://, https://, or ftp:// + replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim + replacedText = inputText.replace(replacePattern1, + "<a href=\"$1\" target=\"_blank\">$1</a>") - htmlContent = '<div>' + htmlContent + '</div>' - '\nContent-Type: text/html; charset=UTF-8' + - '\nContent-Transfer-Encoding: base64' + - '\n\n' + (Base64.encode htmlContent, true).replace(/.{76}/g, "$&\n") + #URLs starting with "www." (without // before it, or it'd re-link the ones done above). + replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim + replacedText = replacedText.replace(replacePattern2, + "$1<a href=\"http://$2\" target=\"_blank\">$2</a>") - createAlternative = (text, html) -> - boundary = getBoundary() + replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim + replacedText = replacedText.replace(replacePattern3, + '<a href="mailto:$1">$1</a>') - '\nContent-Type: multipart/alternative; boundary=' + boundary + - '\n\n--' + boundary + text + - '\n\n--' + boundary + html + - '\n\n--' + boundary + '--' + replacedText - createCids = (cids) -> - return if !cids - cidArr = [] - for cid in cids - type = cid.type - name = cid.name - base64 = cid.base64 - id = getBoundary() + getBoundary = -> + _random = -> Math.random().toString(36).slice(2) + _random() + _random() - cidArr.push '\nContent-Type: ' + type + '; name=\"' + name + '\"' + + createPlain = (textContent = '') -> + '\nContent-Type: text/plain; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + - '\nContent-ID: <' + id + '>' + - '\nX-Attachment-Id: ' + id + - '\n\n' + base64 - cidArr - - createRelated = (alternative, cids = []) -> - boundary = getBoundary() - - relatedStr = '\nContent-Type: multipart/related; boundary=' + boundary + - '\n\n--' + boundary + alternative - for cid in cids - relatedStr += ('\n--' + boundary + cid) - - relatedStr + '\n--' + boundary + '--' - - createAttaches = (attaches) -> - return if !attaches - result = [] - for attach in attaches - type = attach.type - name = attach.name - base64 = attach.base64 - id = getBoundary() - - result.push '\nContent-Type: ' + type + '; name=\"' + name + '\"' + - '\nContent-Disposition: attachment; filename=\"' + name + '\"' + + '\n\n' + (Base64.encode textContent, true).replace(/.{76}/g, "$&\n") + + createHtml = (msg) -> + htmlContent = msg.body || "" + htmlContent = htmlContent.replace(/&/g, '&amp;').replace(/</g, '&lt;') + .replace(/>/, '&gt;').replace(/\n/g, '\n<br/>') + + htmlContent = linkify(htmlContent) + + htmlContent = '<div>' + htmlContent + '</div>' + '\nContent-Type: text/html; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + - '\nX-Attachment-Id: ' + id + - '\n\n' + base64 - result + '\n\n' + (Base64.encode htmlContent, true).replace(/.{76}/g, "$&\n") + + createAlternative = (text, html) -> + boundary = getBoundary() + + '\nContent-Type: multipart/alternative; boundary=' + boundary + + '\n\n--' + boundary + text + + '\n\n--' + boundary + html + + '\n\n--' + boundary + '--' + + createCids = (cids) -> + return if !cids + cidArr = [] + for cid in cids + type = cid.type + name = cid.name + base64 = cid.base64 + id = getBoundary() + + cidArr.push '\nContent-Type: ' + type + '; name=\"' + name + '\"' + + '\nContent-Transfer-Encoding: base64' + + '\nContent-ID: <' + id + '>' + + '\nX-Attachment-Id: ' + id + + '\n\n' + base64 + cidArr + + createRelated = (alternative, cids = []) -> + boundary = getBoundary() + + relatedStr = '\nContent-Type: multipart/related; boundary=' + boundary + + '\n\n--' + boundary + alternative + for cid in cids + relatedStr += ('\n--' + boundary + cid) + + relatedStr + '\n--' + boundary + '--' + + createAttaches = (attaches) -> + return if !attaches + result = [] + for attach in attaches + type = attach.type + name = attach.name + base64 = attach.base64 + id = getBoundary() + + result.push '\nContent-Type: ' + type + '; name=\"' + name + '\"' + + '\nContent-Disposition: attachment; filename=\"' + name + '\"' + + '\nContent-Transfer-Encoding: base64' + + '\nX-Attachment-Id: ' + id + + '\n\n' + base64 + result + + createMixed = (related, attaches) -> + boundary = getBoundary() + subject = '' + if mail.subject + subject = '=?UTF-8?B?' + Base64.encode(mail.subject, true) + '?=' + + mailFromName = '=?UTF-8?B?' + Base64.encode(mail.fromName || "", + true) + '?=' + date = (new Date().toGMTString()).replace(/GMT|UTC/gi, '+0000') + mimeStr = 'MIME-Version: 1.0' + + '\nDate: ' + date + + '\nMessage-ID: <' + getBoundary() + '@mail.your-domain.com>' + + '\nSubject: ' + subject + + '\nFrom: ' + mailFromName + ' <' + mail.from + '>' + + '\nTo: ' + mail.to + + '\nContent-Type: multipart/mixed; boundary=' + boundary + + '\n\n--' + boundary + related + + for attach in attaches + mimeStr += ('\n--' + boundary + attach) + + (mimeStr + '\n--' + boundary + '--').replace /\n/g, '\r\n' + - createMixed = (related, attaches, mail) -> - boundary = getBoundary() - subject = '' - if mail.subject - subject = '=?UTF-8?B?' + Base64.encode(mail.subject, true) + '?=' - - mailFromName = '=?UTF-8?B?' + Base64.encode(mail.fromName || "", - true) + '?=' - date = (new Date().toGMTString()).replace(/GMT|UTC/gi, '+0000') - mimeStr = 'MIME-Version: 1.0' + - '\nDate: ' + date + - '\nMessage-ID: <' + getBoundary() + '@mail.your-domain.com>' + - '\nSubject: ' + subject + - '\nFrom: ' + mailFromName + ' <' + mail.from + '>' + - '\nTo: ' + mail.to + - '\nContent-Type: multipart/mixed; boundary=' + boundary + - '\n\n--' + boundary + related - - for attach in attaches - mimeStr += ('\n--' + boundary + attach) - - (mimeStr + '\n--' + boundary + '--').replace /\n/g, '\r\n' - - createMimeStr = (mail) -> plain = createPlain mail.body htm = createHtml mail alternative = createAlternative plain, htm @@ -127,11 +140,339 @@ window.Mime = do -> related = createRelated alternative, cids attaches = createAttaches mail.attaches - result = createMixed related, attaches, mail + result = createMixed(related, attaches) + + result + + + # ********************************* + # MailParser helper + + MailParser = (rawMessage) -> + explodeMessage = (inMessage) -> + inHeaderPos = inMessage.indexOf("\r\n\r\n") + if inHeaderPos is -1 + inMessage = inMessage.replace(/\n/g, "\r\n") # Let's give it a try + inHeaderPos = inMessage.indexOf("\r\n\r\n") + # empty body + inHeaderPos = inMessage.length if inHeaderPos is -1 + + inRawHeaders = inMessage.slice(0, inHeaderPos).replace(/\r\n\s+/g, " ") + "\r\n" + inRawBody = inMessage.slice(inHeaderPos).replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, "") + inContentType = "" + regContentType = inRawHeaders.match(/Content-Type: (.*)/i) + + if regContentType and regContentType.length > 0 + inContentType = regContentType[1] # ignore case-sensitive Content-type + else + console.log "Warning: MailParser: Content-type doesn't exist!" + + inContentTypeParts = inContentType.split(";") + mimeType = inContentTypeParts[0].replace(/\s/g, "") + mimeTypeParts = mimeType.split("/") + + # If it's a multipart we need to split it up + if mimeTypeParts[0].toLowerCase() is "multipart" + inBodyParts = [] + + #MS sends boundary in 3rd element + match = inContentTypeParts[1].match(/boundary="?([^"]*)"?/i) + match = inContentTypeParts[2].match(/boundary="?([^"]*)"?/i) if not match and inContentTypeParts[2] + inBoundary = _util.trim(match[1]).replace(/"/g, "") + escBoundary = inBoundary.replace(/\+/g, "\\+") # We should escape '+' sign + regString = new RegExp("--" + escBoundary, "g") + inBodyParts = inRawBody.replace(regString, inBoundary).replace(regString, inBoundary).split(inBoundary) + inBodyParts.shift() + inBodyParts.pop() + i = 0 + + while i < inBodyParts.length + inBodyParts[i] = inBodyParts[i].replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, "") + inBodyParts[i] = explodeMessage(inBodyParts[i]) + i++ + else + inBody = inRawBody + if mimeTypeParts[0] is "text" + inBody = inBody.replace(RegExp("=\\r\\n", "g"), "") + specialChars = inBody.match(RegExp("=[A-F0-9][A-F0-9]", "g")) + if specialChars + i = 0 + + while i < specialChars.length + inBody = inBody.replace(specialChars[i], + String.fromCharCode(parseInt(specialChars[i].replace(RegExp("="), ""), 16))) + i++ + + rawHeaders: inRawHeaders + rawBody: inRawBody + body: inBody + contentType: inContentType + contentTypeParts: inContentTypeParts + boundary: inBoundary + bodyParts: inBodyParts + mimeType: mimeType + mimeTypeParts: mimeTypeParts + + messageParts = "" + try + messageParts = explodeMessage(rawMessage) + rawHeaders = messageParts.rawHeaders + getValidStr = (arr = []) -> + arr[1] or "" + + subject = getValidStr((/\r\nSubject: (.*)\r\n/g).exec(rawHeaders)) + to = getValidStr((/\r\nTo: (.*)\r\n/g).exec(rawHeaders)) + from = getValidStr((/\r\nFrom: (.*)\r\n/g).exec(rawHeaders)) + + { + messageParts: messageParts + subject: subject + to: to + from: from + } + + + # ****************************** + # Local Utility + + _util = do -> + trim = (str = '') -> + str.trim?() || str.replace(/^\s+|\s+$/g, '') + + decode = (txt = '', charset = '') -> + charset = charset.toLowerCase() + result = switch + when charset.indexOf('koi8-r') isnt -1 then KOIRDec(txt) + when charset.indexOf('utf-8') isnt -1 then Base64._utf8_decode(txt) + when charset.indexOf('windows-1251') isnt -1 then win1251Dec(txt) + else + txt + + result + + # QuotedPrintable Decode + QPDec = (s) -> + s.replace(/\=[\r\n]+/g, "").replace(/\=[0-9A-F]{2}/gi, (v) -> + String.fromCharCode(parseInt(v.substr(1), 16))) + + KOIRDec = (str) -> + charmap = unescape( + "%u2500%u2502%u250C%u2510%u2514%u2518%u251C%u2524%u252C%u2534%u253C%u2580%u2584%u2588%u258C%u2590" + + "%u2591%u2592%u2593%u2320%u25A0%u2219%u221A%u2248%u2264%u2265%u00A0%u2321%u00B0%u00B2%u00B7%u00F7" + + "%u2550%u2551%u2552%u0451%u2553%u2554%u2555%u2556%u2557%u2558%u2559%u255A%u255B%u255C%u255D%u255E" + + "%u255F%u2560%u2561%u0401%u2562%u2563%u2564%u2565%u2566%u2567%u2568%u2569%u256A%u256B%u256C%u00A9" + + "%u044E%u0430%u0431%u0446%u0434%u0435%u0444%u0433%u0445%u0438%u0439%u043A%u043B%u043C%u043D%u043E" + + "%u043F%u044F%u0440%u0441%u0442%u0443%u0436%u0432%u044C%u044B%u0437%u0448%u044D%u0449%u0447%u044A" + + "%u042E%u0410%u0411%u0426%u0414%u0415%u0424%u0413%u0425%u0418%u0419%u041A%u041B%u041C%u041D%u041E" + + "%u041F%u042F%u0420%u0421%u0422%u0423%u0416%u0412%u042C%u042B%u0417%u0428%u042D%u0429%u0427%u042A") + code2char = (code) -> + return charmap.charAt(code - 0x80) if code >= 0x80 and code <= 0xFF + String.fromCharCode(code) + res = "" + for val, i in str + res = res + code2char str.charCodeAt i + + res + + win1251Dec = (str = '') -> + result = '' + for s, i in str + iCode = str.charCodeAt(i) + oCode = switch + when iCode is 168 then 1025 + when iCode is 184 then 1105 + when 191 < iCode < 256 then iCode + 848 + else + iCode + result = result + String.fromCharCode(oCode) + + result + + _decodeMimeWord = (str, toCharset) -> + str = _util.trim(str) + fromCharset = undefined + encoding = undefined + match = undefined + match = str.match(/^\=\?([\w_\-]+)\?([QqBb])\?([^\?]*)\?\=$/i) + return decode(str, toCharset) unless match + + fromCharset = match[1] + encoding = (match[2] or "Q").toString().toUpperCase() + str = (match[3] or "").replace(/_/g, " ") + if encoding is "B" + Base64.decode str, toCharset #, fromCharset + else if encoding is "Q" + QPDec str #, toCharset, fromCharset + else + str + + decodeMimeWords = (str, toCharset) -> +# curCharset = undefined + str = (str or "").toString().replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, "$1") + .replace(/\=\?([\w_\-]+)\?([QqBb])\?[^\?]*\?\=/g, ((mimeWord, charset, encoding) -> +# curCharset = charset + encoding + _decodeMimeWord mimeWord #, curCharset + ).bind(this)) + + decode str, toCharset + + toHtmlEntity = (txt = "") -> + (txt + "").replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + + {decode, KOIRDec, win1251Dec, decodeMimeWords, toHtmlEntity, trim} + + + # ********************************* + # Create Mail Object from Mime Text + + + buildMimeObj = (rawMailObj) -> + readyMail = + html: "" + text: "" + attaches: [] + innerMsgs: [] + to: _util.decodeMimeWords(rawMailObj.to) + from: _util.decodeMimeWords rawMailObj.from + subject: _util.decodeMimeWords rawMailObj.subject + + decodeBody = (body, rawHeaders) -> + isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders) + isBase64 = /Content-Transfer-Encoding: base64/i.test(rawHeaders) + if isBase64 + body = body.replace(/\s/g, '') + decBody = atob?(body) + decBody ?= Base64.decode(body) + body = decBody + else if isQP + body = _util.QPDec body + + body + + parseBodyParts = (bodyParts) -> + return if !bodyParts + for part in bodyParts + mimeType = (part.mimeType ? "").toLowerCase() + if mimeType.indexOf('multipart') isnt -1 + parseBodyParts part.bodyParts + continue + + if mimeType.indexOf('message/rfc822') isnt -1 + newMimeMsg = MailParser(part.rawBody) + innerMsg = toMimeObj(newMimeMsg) + readyMail.innerMsgs.push innerMsg + # txt = innerMsg.text + # htm = innerMsg.html + # readyMail.text += txt if txt + # readyMail.html += htm if htm + # if innerMsg.attaches?.length > 0 + # readyMail.attaches = readyMail.attaches.concat(innerMsg.attaches) + continue + + rawHeaders = part.rawHeaders + isAttach = rawHeaders.indexOf('Content-Disposition: attachment') isnt -1 + body = part.rawBody + + isHtml = /text\/html/.test(mimeType) + isPlain = /text\/plain/.test(mimeType) + isImg = /image/.test(mimeType) + isAudio = /audio/.test(mimeType) + # isBase64 = /Content-Transfer-Encoding: base64/i.test(rawHeaders) + + if isAttach or isImg or isAudio + isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders) + if isQP + body = _util.QPDec body + body = if btoa then btoa(body) else Base64.encode(body) + +# name = null + for typePart in part.contentTypeParts + if /name=/i.test(typePart) + name = typePart.replace(/(.*)=/, '').replace(/"|'/g, '') + break + + if !name + name = if isImg then "image" else if isAudio then "audio" else "attachment" + name += "_" + Math.floor(Math.random() * 100) + slashPos = mimeType.indexOf('/') + + type = mimeType.substring(slashPos + 1) + + if type.length < 4 + name += "." + type + + regex = /(.*)content-id:(.*)<(.*)>/i + attach = + type: mimeType + base64: body + name: name + cid: regex.exec(rawHeaders)?[3] + visible: /png|jpeg|jpg|gif/.test(mimeType) + + readyMail.attaches.push attach + + else if isHtml or isPlain + body = decodeBody body, rawHeaders + body = _util.decode(body, part.contentType) + readyMail.html += body if isHtml + readyMail.text += body if isPlain + + else + console.log "Unknown mime type: #{mimeType}" + + null + + try + parts = rawMailObj.messageParts + if !parts + return readyMail + + mimeType = (parts.mimeType || "").toLowerCase() + isText = /text\/plain/.test(mimeType) + isHtml = /text\/html/.test(mimeType) + + if mimeType.indexOf('multipart') isnt -1 + parseBodyParts parts.bodyParts + else if isText or isHtml + body = decodeBody parts.body, parts.rawHeaders + body = _util.decode body, parts.contentType + readyMail.html = body if isHtml + readyMail.text = body if isText + else + console.log "Warning: mime type isn't supported! mime=#{mimeType}" + + catch err + throw new Error err + + wrapPreTag = (txt) -> + "<pre>" + _util.toHtmlEntity(txt) + "</pre>" + + mergeInnerMsgs = (mail) -> + innerMsgs = mail.innerMsgs + if innerMsgs?.length + if !_util.trim(mail.html) and mail.text + mail.html += wrapPreTag mail.text + + for innerMsg in innerMsgs + msg = mergeInnerMsgs innerMsg + txt = msg.text + htm = msg.html + if htm + mail.html += htm + else if txt + mail.html += wrapPerTag txt + mail.text += txt + if msg.attaches?.length > 0 + mail.attaches = mail.attaches.concat(msg.attaches) + + mail + result = mergeInnerMsgs readyMail result - { - toMimeTxt: createMimeStr - } + toMimeObj = (mimeMsgText) -> + rawMailObj = MailParser mimeMsgText + mailObj = buildMimeObj rawMailObj + mailObj + { toMimeTxt, toMimeObj }