mime-js.js (20649B)
1 2 /* 3 mime-js.js 0.2.0 4 2014-10-18 5 6 By Ikrom, https://github.com/ikr0m 7 License: X11/MIT 8 */ 9 10 (function() { 11 window.Mime = (function() { 12 var MailParser, _util, buildMimeObj, toMimeObj, toMimeTxt; 13 toMimeTxt = function(mail, txtOnly) { 14 var alternative, attaches, cids, createAlternative, createAttaches, createCids, createHtml, createMixed, createPlain, createRelated, getBoundary, htm, linkify, plain, related, result; 15 linkify = function(inputText) { 16 var replacePattern1, replacePattern2, replacePattern3, replacedText; 17 replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim; 18 replacedText = inputText.replace(replacePattern1, "<a href=\"$1\" target=\"_blank\">$1</a>"); 19 replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim; 20 replacedText = replacedText.replace(replacePattern2, "$1<a href=\"http://$2\" target=\"_blank\">$2</a>"); 21 replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim; 22 replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>'); 23 return replacedText; 24 }; 25 getBoundary = function() { 26 var _random; 27 _random = function() { 28 return Math.random().toString(36).slice(2); 29 }; 30 return _random() + _random(); 31 }; 32 createPlain = function(textContent) { 33 if (textContent == null) { 34 textContent = ''; 35 } 36 return '\nContent-Type: text/plain; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + '\n\n' + (Base64.encode(textContent, true)).replace(/.{76}/g, "$&\n"); 37 }; 38 createHtml = function(msg) { 39 var htmlContent; 40 htmlContent = msg.body || ""; 41 htmlContent = htmlContent.replace(/&/g, '&').replace(/</g, '<').replace(/>/, '>').replace(/\n/g, '\n<br/>'); 42 htmlContent = linkify(htmlContent); 43 htmlContent = '<div>' + htmlContent + '</div>'; 44 return '\nContent-Type: text/html; charset=UTF-8' + '\nContent-Transfer-Encoding: base64' + '\n\n' + (Base64.encode(htmlContent, true)).replace(/.{76}/g, "$&\n"); 45 }; 46 createAlternative = function(text, html) { 47 var boundary; 48 boundary = getBoundary(); 49 return '\nContent-Type: multipart/alternative; boundary=' + boundary + '\n\n--' + boundary + text + '\n\n--' + boundary + html + '\n\n--' + boundary + '--'; 50 }; 51 createCids = function(cids) { 52 var base64, cid, cidArr, id, j, len, name, type; 53 if (!cids) { 54 return; 55 } 56 cidArr = []; 57 for (j = 0, len = cids.length; j < len; j++) { 58 cid = cids[j]; 59 type = cid.type; 60 name = cid.name; 61 base64 = cid.base64; 62 id = getBoundary(); 63 cidArr.push('\nContent-Type: ' + type + '; name=\"' + name + '\"' + '\nContent-Transfer-Encoding: base64' + '\nContent-ID: <' + id + '>' + '\nX-Attachment-Id: ' + id + '\n\n' + base64); 64 } 65 return cidArr; 66 }; 67 createRelated = function(alternative, cids) { 68 var boundary, cid, j, len, relatedStr; 69 if (cids == null) { 70 cids = []; 71 } 72 boundary = getBoundary(); 73 relatedStr = '\nContent-Type: multipart/related; boundary=' + boundary + '\n\n--' + boundary + alternative; 74 for (j = 0, len = cids.length; j < len; j++) { 75 cid = cids[j]; 76 relatedStr += '\n--' + boundary + cid; 77 } 78 return relatedStr + '\n--' + boundary + '--'; 79 }; 80 createAttaches = function(attaches) { 81 var attach, content, id, j, len, name, part, result, type; 82 if (!attaches) { 83 return; 84 } 85 result = []; 86 for (j = 0, len = attaches.length; j < len; j++) { 87 attach = attaches[j]; 88 type = attach.type; 89 name = attach.name; 90 id = getBoundary(); 91 content = ''; 92 part = '\nContent-Type: ' + type; 93 if (name) { 94 part += '; name=\"' + name + '\"' + '\nContent-Disposition: attachment; filename=\"' + name + '\"'; 95 } 96 if (attach.base64) { 97 content = attach.base64; 98 part += '\nContent-Transfer-Encoding: base64'; 99 } else { 100 content = attach.raw; 101 } 102 part += '\nX-Attachment-Id: ' + id; 103 part += '\n\n' + content; 104 result.push(part); 105 } 106 return result; 107 }; 108 createMixed = function(related, attaches) { 109 var attach, boundary, date, j, len, mailFromName, mimeStr, subject; 110 boundary = getBoundary(); 111 subject = ''; 112 if (mail.subject) { 113 subject = '=?UTF-8?B?' + Base64.encode(mail.subject, true) + '?='; 114 } 115 mailFromName = '=?UTF-8?B?' + Base64.encode(mail.fromName || "", true) + '?='; 116 date = (new Date().toGMTString()).replace(/GMT|UTC/gi, '+0000'); 117 mimeStr = 'MIME-Version: 1.0' + '\nDate: ' + date + '\nMessage-ID: <' + getBoundary() + '@mail.your-domain.com>' + '\nSubject: ' + subject + '\nFrom: ' + mailFromName + ' <' + mail.from + '>' + (mail.to ? '\nTo: ' + mail.to : '') + (mail.cc ? '\nCc: ' + mail.cc : '') + '\nContent-Type: multipart/mixed; boundary=' + boundary + '\n\n--' + boundary + related; 118 for (j = 0, len = attaches.length; j < len; j++) { 119 attach = attaches[j]; 120 mimeStr += '\n--' + boundary + attach; 121 } 122 return (mimeStr + '\n--' + boundary + '--').replace(/\n/g, '\r\n'); 123 }; 124 plain = createPlain(mail.body); 125 if (txtOnly) { 126 related = plain; 127 } else { 128 htm = createHtml(mail); 129 alternative = createAlternative(plain, htm); 130 cids = createCids(mail.cids); 131 related = createRelated(alternative, cids); 132 } 133 attaches = createAttaches(mail.attaches); 134 result = createMixed(related, attaches); 135 return result; 136 }; 137 MailParser = function(rawMessage) { 138 var cc, explodeMessage, from, getValidStr, messageParts, rawHeaders, subject, to; 139 explodeMessage = function(inMessage) { 140 var escBoundary, i, inBody, inBodyParts, inBoundary, inContentType, inContentTypeParts, inHeaderPos, inRawBody, inRawHeaders, match, mimeType, mimeTypeParts, regContentType, regString, specialChars; 141 inHeaderPos = inMessage.indexOf("\r\n\r\n"); 142 if (inHeaderPos === -1) { 143 inMessage = inMessage.replace(/\n/g, "\r\n"); 144 inHeaderPos = inMessage.indexOf("\r\n\r\n"); 145 if (inHeaderPos === -1) { 146 inHeaderPos = inMessage.length; 147 } 148 } 149 inRawHeaders = inMessage.slice(0, inHeaderPos).replace(/\r\n\s+/g, " ") + "\r\n"; 150 inRawBody = inMessage.slice(inHeaderPos).replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, ""); 151 inContentType = ""; 152 regContentType = inRawHeaders.match(/Content-Type: (.*)/i); 153 if (regContentType && regContentType.length > 0) { 154 inContentType = regContentType[1]; 155 } else { 156 console.log("Warning: MailParser: Content-type doesn't exist!"); 157 } 158 inContentTypeParts = inContentType.split(";"); 159 mimeType = inContentTypeParts[0].replace(/\s/g, ""); 160 mimeTypeParts = mimeType.split("/"); 161 if (mimeTypeParts[0].toLowerCase() === "multipart") { 162 inBodyParts = []; 163 match = inContentTypeParts[1].match(/boundary="?([^"]*)"?/i); 164 if (!match && inContentTypeParts[2]) { 165 match = inContentTypeParts[2].match(/boundary="?([^"]*)"?/i); 166 } 167 inBoundary = _util.trim(match[1]).replace(/"/g, ""); 168 escBoundary = inBoundary.replace(/\+/g, "\\+"); 169 regString = new RegExp("--" + escBoundary, "g"); 170 inBodyParts = inRawBody.replace(regString, inBoundary).replace(regString, inBoundary).split(inBoundary); 171 inBodyParts.shift(); 172 inBodyParts.pop(); 173 i = 0; 174 while (i < inBodyParts.length) { 175 inBodyParts[i] = inBodyParts[i].replace(/(\r\n)+$/, "").replace(/^(\r\n)+/, ""); 176 inBodyParts[i] = explodeMessage(inBodyParts[i]); 177 i++; 178 } 179 } else { 180 inBody = inRawBody; 181 if (mimeTypeParts[0] === "text") { 182 inBody = inBody.replace(RegExp("=\\r\\n", "g"), ""); 183 specialChars = inBody.match(RegExp("=[A-F0-9][A-F0-9]", "g")); 184 if (specialChars) { 185 i = 0; 186 while (i < specialChars.length) { 187 inBody = inBody.replace(specialChars[i], String.fromCharCode(parseInt(specialChars[i].replace(RegExp("="), ""), 16))); 188 i++; 189 } 190 } 191 } 192 } 193 return { 194 rawHeaders: inRawHeaders, 195 rawBody: inRawBody, 196 body: inBody, 197 contentType: inContentType, 198 contentTypeParts: inContentTypeParts, 199 boundary: inBoundary, 200 bodyParts: inBodyParts, 201 mimeType: mimeType, 202 mimeTypeParts: mimeTypeParts 203 }; 204 }; 205 messageParts = ""; 206 try { 207 messageParts = explodeMessage(rawMessage); 208 } catch (error) {} 209 rawHeaders = messageParts.rawHeaders; 210 getValidStr = function(arr) { 211 if (arr == null) { 212 arr = []; 213 } 214 return arr[1] || ""; 215 }; 216 subject = getValidStr(/\r\nSubject: (.*)\r\n/g.exec(rawHeaders)); 217 to = getValidStr(/\r\nTo: (.*)\r\n/g.exec(rawHeaders)); 218 cc = getValidStr(/\r\nCc: (.*)\r\n/g.exec(rawHeaders)); 219 from = getValidStr(/\r\nFrom: (.*)\r\n/g.exec(rawHeaders)); 220 return { 221 messageParts: messageParts, 222 subject: subject, 223 to: to, 224 cc: cc, 225 from: from 226 }; 227 }; 228 _util = (function() { 229 var KOIRDec, QPDec, _decodeMimeWord, decode, decodeMimeWords, toHtmlEntity, trim, win1251Dec; 230 trim = function(str) { 231 if (str == null) { 232 str = ''; 233 } 234 return (typeof str.trim === "function" ? str.trim() : void 0) || str.replace(/^\s+|\s+$/g, ''); 235 }; 236 decode = function(txt, charset) { 237 var result; 238 if (txt == null) { 239 txt = ''; 240 } 241 if (charset == null) { 242 charset = ''; 243 } 244 charset = charset.toLowerCase(); 245 result = (function() { 246 switch (false) { 247 case charset.indexOf('koi8-r') === -1: 248 return KOIRDec(txt); 249 case charset.indexOf('utf-8') === -1: 250 return Base64._utf8_decode(txt); 251 case charset.indexOf('windows-1251') === -1: 252 return win1251Dec(txt); 253 default: 254 return txt; 255 } 256 })(); 257 return result; 258 }; 259 QPDec = function(s) { 260 return s.replace(/\=[\r\n]+/g, "").replace(/\=[0-9A-F]{2}/gi, function(v) { 261 return String.fromCharCode(parseInt(v.substr(1), 16)); 262 }); 263 }; 264 KOIRDec = function(str) { 265 var charmap, code2char, i, j, len, res, val; 266 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"); 267 code2char = function(code) { 268 if (code >= 0x80 && code <= 0xFF) { 269 return charmap.charAt(code - 0x80); 270 } 271 return String.fromCharCode(code); 272 }; 273 res = ""; 274 for (i = j = 0, len = str.length; j < len; i = ++j) { 275 val = str[i]; 276 res = res + code2char(str.charCodeAt(i)); 277 } 278 return res; 279 }; 280 win1251Dec = function(str) { 281 var i, iCode, j, len, oCode, result, s; 282 if (str == null) { 283 str = ''; 284 } 285 result = ''; 286 for (i = j = 0, len = str.length; j < len; i = ++j) { 287 s = str[i]; 288 iCode = str.charCodeAt(i); 289 oCode = (function() { 290 switch (false) { 291 case iCode !== 168: 292 return 1025; 293 case iCode !== 184: 294 return 1105; 295 case !((191 < iCode && iCode < 256)): 296 return iCode + 848; 297 default: 298 return iCode; 299 } 300 })(); 301 result = result + String.fromCharCode(oCode); 302 } 303 return result; 304 }; 305 _decodeMimeWord = function(str, toCharset) { 306 var encoding, fromCharset, match; 307 str = _util.trim(str); 308 fromCharset = void 0; 309 encoding = void 0; 310 match = void 0; 311 match = str.match(/^\=\?([\w_\-]+)\?([QqBb])\?([^\?]*)\?\=$/i); 312 if (!match) { 313 return decode(str, toCharset); 314 } 315 fromCharset = match[1]; 316 encoding = (match[2] || "Q").toString().toUpperCase(); 317 str = (match[3] || "").replace(/_/g, " "); 318 if (encoding === "B") { 319 return Base64.decode(str, toCharset); 320 } else if (encoding === "Q") { 321 return QPDec(str); 322 } else { 323 return str; 324 } 325 }; 326 decodeMimeWords = function(str, toCharset) { 327 str = (str || "").toString().replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, "$1").replace(/\=\?([\w_\-]+)\?([QqBb])\?[^\?]*\?\=/g, (function(mimeWord, charset, encoding) { 328 return _decodeMimeWord(mimeWord); 329 }).bind(this)); 330 return decode(str, toCharset); 331 }; 332 toHtmlEntity = function(txt) { 333 if (txt == null) { 334 txt = ""; 335 } 336 return (txt + "").replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 337 }; 338 return { 339 decode: decode, 340 KOIRDec: KOIRDec, 341 win1251Dec: win1251Dec, 342 decodeMimeWords: decodeMimeWords, 343 toHtmlEntity: toHtmlEntity, 344 trim: trim 345 }; 346 })(); 347 buildMimeObj = function(rawMailObj) { 348 var body, decodeBody, err, isHtml, isText, mergeInnerMsgs, mimeType, parseBodyParts, parts, readyMail, result, wrapPreTag; 349 readyMail = { 350 html: "", 351 text: "", 352 attaches: [], 353 innerMsgs: [], 354 to: _util.decodeMimeWords(rawMailObj.to), 355 cc: _util.decodeMimeWords(rawMailObj.cc), 356 from: _util.decodeMimeWords(rawMailObj.from), 357 subject: _util.decodeMimeWords(rawMailObj.subject) 358 }; 359 decodeBody = function(body, rawHeaders) { 360 var decBody, isBase64, isQP; 361 isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders); 362 isBase64 = /Content-Transfer-Encoding: base64/i.test(rawHeaders); 363 if (isBase64) { 364 body = body.replace(/\s/g, ''); 365 decBody = typeof atob === "function" ? atob(body) : void 0; 366 if (decBody == null) { 367 decBody = Base64.decode(body); 368 } 369 body = decBody; 370 } else if (isQP) { 371 body = _util.QPDec(body); 372 } 373 return body; 374 }; 375 parseBodyParts = function(bodyParts) { 376 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; 377 if (!bodyParts) { 378 return; 379 } 380 for (j = 0, len = bodyParts.length; j < len; j++) { 381 part = bodyParts[j]; 382 mimeType = ((ref = part.mimeType) != null ? ref : "").toLowerCase(); 383 if (mimeType.indexOf('multipart') !== -1) { 384 parseBodyParts(part.bodyParts); 385 continue; 386 } 387 if (mimeType.indexOf('message/rfc822') !== -1) { 388 newMimeMsg = MailParser(part.rawBody); 389 innerMsg = toMimeObj(newMimeMsg); 390 readyMail.innerMsgs.push(innerMsg); 391 continue; 392 } 393 rawHeaders = part.rawHeaders; 394 isAttach = rawHeaders.indexOf('Content-Disposition: attachment') !== -1; 395 body = part.rawBody; 396 isHtml = /text\/html/.test(mimeType); 397 isPlain = /text\/plain/.test(mimeType); 398 isImg = /image/.test(mimeType); 399 isAudio = /audio/.test(mimeType); 400 if (isAttach || isImg || isAudio) { 401 isQP = /Content-Transfer-Encoding: quoted-printable/i.test(rawHeaders); 402 if (isQP) { 403 body = _util.QPDec(body); 404 body = btoa ? btoa(body) : Base64.encode(body); 405 } 406 ref1 = part.contentTypeParts; 407 for (k = 0, len1 = ref1.length; k < len1; k++) { 408 typePart = ref1[k]; 409 if (/name=/i.test(typePart)) { 410 name = typePart.replace(/(.*)=/, '').replace(/"|'/g, ''); 411 break; 412 } 413 } 414 if (!name) { 415 name = isImg ? "image" : isAudio ? "audio" : "attachment"; 416 name += "_" + Math.floor(Math.random() * 100); 417 slashPos = mimeType.indexOf('/'); 418 type = mimeType.substring(slashPos + 1); 419 if (type.length < 4) { 420 name += "." + type; 421 } 422 } 423 regex = /(.*)content-id:(.*)<(.*)>/i; 424 attach = { 425 type: mimeType, 426 base64: body, 427 name: name, 428 cid: (ref2 = regex.exec(rawHeaders)) != null ? ref2[3] : void 0, 429 visible: /png|jpeg|jpg|gif/.test(mimeType) 430 }; 431 readyMail.attaches.push(attach); 432 } else if (isHtml || isPlain) { 433 body = decodeBody(body, rawHeaders); 434 body = _util.decode(body, part.contentType); 435 if (isHtml) { 436 readyMail.html += body; 437 } 438 if (isPlain) { 439 readyMail.text += body; 440 } 441 } else { 442 console.log("Unknown mime type: " + mimeType); 443 } 444 } 445 return null; 446 }; 447 try { 448 parts = rawMailObj.messageParts; 449 if (!parts) { 450 return readyMail; 451 } 452 mimeType = (parts.mimeType || "").toLowerCase(); 453 isText = /text\/plain/.test(mimeType); 454 isHtml = /text\/html/.test(mimeType); 455 if (mimeType.indexOf('multipart') !== -1) { 456 parseBodyParts(parts.bodyParts); 457 } else if (isText || isHtml) { 458 body = decodeBody(parts.body, parts.rawHeaders); 459 body = _util.decode(body, parts.contentType); 460 if (isHtml) { 461 readyMail.html = body; 462 } 463 if (isText) { 464 readyMail.text = body; 465 } 466 } else { 467 console.log("Warning: mime type isn't supported! mime=" + mimeType); 468 } 469 } catch (error) { 470 err = error; 471 throw new Error(err); 472 } 473 wrapPreTag = function(txt) { 474 return "<pre>" + _util.toHtmlEntity(txt) + "</pre>"; 475 }; 476 mergeInnerMsgs = function(mail) { 477 var htm, innerMsg, innerMsgs, j, len, msg, ref, txt; 478 innerMsgs = mail.innerMsgs; 479 if (innerMsgs != null ? innerMsgs.length : void 0) { 480 if (!_util.trim(mail.html) && mail.text) { 481 mail.html += wrapPreTag(mail.text); 482 } 483 for (j = 0, len = innerMsgs.length; j < len; j++) { 484 innerMsg = innerMsgs[j]; 485 msg = mergeInnerMsgs(innerMsg); 486 txt = msg.text; 487 htm = msg.html; 488 if (htm) { 489 mail.html += htm; 490 } else if (txt) { 491 mail.html += wrapPerTag(txt); 492 mail.text += txt; 493 } 494 if (((ref = msg.attaches) != null ? ref.length : void 0) > 0) { 495 mail.attaches = mail.attaches.concat(msg.attaches); 496 } 497 } 498 } 499 return mail; 500 }; 501 result = mergeInnerMsgs(readyMail); 502 return result; 503 }; 504 toMimeObj = function(mimeMsgText) { 505 var mailObj, rawMailObj; 506 rawMailObj = MailParser(mimeMsgText); 507 mailObj = buildMimeObj(rawMailObj); 508 return mailObj; 509 }; 510 return { 511 toMimeTxt: toMimeTxt, 512 toMimeObj: toMimeObj 513 }; 514 })(); 515 516 }).call(this);