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:
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, '&').replace(/</g, '<').replace(/>/, '>').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, '&').replace(/</g, '<').replace(/>/g, '>');
+ };
+ 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,"&").replace(/</g,"<").replace(/>/,">").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,"&").replace(/</g,"<").replace(/>/g,">")},{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, '&').replace(/</g, '<')
- .replace(/>/, '>').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, '&').replace(/</g, '<')
+ .replace(/>/, '>').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, '&').replace(/</g, '<').replace(/>/g, '>')
+
+ {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 }