%PDF- %PDF-
Direktori : /home/opti/workflow/node_modules/mimelib/lib/ |
Current File : /home/opti/workflow/node_modules/mimelib/lib/mimelib.js |
"use strict"; var convert = require("encoding").convert, addressparser = require("addressparser"); /** * Folds a long line according to the RFC 5322 http://tools.ietf.org/html/rfc5322#section-2.1.1 * * @param {String} str Mime string that might need folding * @param {Number} [maxLength=76] max length for a line * @param {Boolean} [foldAnywhere] If true, can fold at any location (ie. in base64) * @param {Boolean} [afterSpace] If true fold after the space (default is before) * @return {String} Folded string */ module.exports.foldLine = function(str, maxLength, foldAnywhere, afterSpace, lineMargin) { if (foldAnywhere) { return addBase64SoftLinebreaks(str, maxLength || 76); } return module.exports.mimeFunctions.foldLine(str, maxLength, !! afterSpace, lineMargin); }; /** * Encodes a string into mime encoded word format http://en.wikipedia.org/wiki/MIME#Encoded-Word * * @param {String} str String to be encoded * @param {String} encoding Encoding Q for quoted printable or B for base64 * @param {String} [charset="UTF-8"] Charset to be used * @param {Number} [maxLength] If set, split on maxLength * @return {String} Mime word encoded string */ module.exports.encodeMimeWord = function(str, encoding, charset, maxLength) { return module.exports.mimeFunctions.encodeMimeWord(str, encoding, maxLength || 0, charset); }; /** * Encodes need parts of a string to mime word format * * @param {String} str String to be encoded * @param {String} encoding Encoding Q for quoted printable or B for base64 * @param {Number} [maxLength] If set, split on maxLength * @param {String} [charset="UTF-8"] Charset to be used * @return {String} String with possible mime word encoded parts */ module.exports.encodeMimeWords = function(str, encoding, maxLength, charset) { return module.exports.mimeFunctions.encodeMimeWords(str, encoding, maxLength || 0, charset); }; /** * Decodes a string from mime encoded word * * @param {String} str Mime word encoded string * @return {String} Decoded string */ module.exports.decodeMimeWord = function(str) { return module.exports.mimeFunctions.decodeMimeWord(str).toString("utf-8"); }; /** * Decodes all mime words from a string to an unencoded string * * @param {String} str String that may include mime words * @return {String} Unencoded string */ module.exports.parseMimeWords = function(str) { return module.exports.mimeFunctions.decodeMimeWords(str).toString("utf-8"); }; /** * Encodes a string into Quoted-printable format. Maximum line length for the * encoded string is always 76+2 bytes * * @param {String} str String to be encoded into Quoted-printable * @param {Boolean} [mimeWord] legacy parameter, not used * @param {String} [charset="UTF-8"] Destination charset * @return {String} Quoted printable encoded string */ module.exports.encodeQuotedPrintable = function(str, mimeWord, charset) { if (typeof mimeWord == "string" && !charset) { charset = mimeWord; mimeWord = undefined; } return module.exports.mimeFunctions.encodeQuotedPrintable(str, charset); }; /** * Decodes a string from Quoted-printable format * * @param {String} str String to be decoded from Quoted-printable * @param {Boolean} [mimeWord] legacy parameter, not used * @param {String} [charset="UTF-8"] Source charset * @return {String} Decoded string */ module.exports.decodeQuotedPrintable = function(str, mimeWord, charset) { if (typeof mimeWord == "string" && !charset) { charset = mimeWord; mimeWord = undefined; } charset = (charset || "").toString().toUpperCase().trim(); var decodedString = module.exports.mimeFunctions.decodeQuotedPrintable(str, "utf-8", charset); return charset == "BINARY" ? decodedString : decodedString.toString("utf-8"); }; /** * Encodes a string into Base64 format. Base64 is mime-word safe * * @param {String} str String to be encoded into Base64 * @param {String} [charset="UTF-8"] Destination charset * @return {String} Base64 encoded string */ module.exports.encodeBase64 = function(str, charset) { return module.exports.mimeFunctions.encodeBase64(str, charset); }; /** * Decodes a string from Base64 format * * @param {String} str String to be decoded from Base64 * @param {String} [charset="UTF-8"] Source charset * @return {String} Decoded string */ module.exports.decodeBase64 = function(str, charset) { return module.exports.mimeFunctions.decodeBase64(str, "utf-8", charset).toString("utf-8"); }; /** * Parses names and addresses from a from, to, cc or bcc line * For example: 'Andris Reinman <andris@tr.ee>, someone@else.com' * will be parsed into: [{name:"Andris Reinman", address:"andris@tr.ee"}, {address: "someone@else.com"}] * * @param {String|Array} addresses Address line string or an array of strings * @return {Array} An array of parsed e-mails addresses in the form of [{name, address}] */ module.exports.parseAddresses = function(addresses) { return [].concat.apply([], [].concat(addresses).map(addressparser)).map(function(address) { address.name = module.exports.parseMimeWords(address.name); if (address.group) { address.group.forEach(function(groupAddress) { groupAddress.name = module.exports.parseMimeWords(groupAddress.name); }); } return address; }); }; /** * Parses header lines into an array of objects. Output: {'x-header': ['value']} * * @param {String} headers Full header part to be parsed * @return {Object} Parsed headers */ module.exports.parseHeaders = function(headers) { return module.exports.mimeFunctions.parseHeaderLines(headers); }; /** * Parses a header line to search for additional parameters. For example * parseHeaderLine('text/plain; charset=utf-8') * will be parsed into * {defaultValue: 'text/plain', charset: 'utf-8'} * * @param {String} line Single header value without key part to be parsed * @return {Object} Parsed value */ module.exports.parseHeaderLine = function(line) { if (!line) { return {}; } var result = {}, parts = line.split(";"), pos; for (var i = 0, len = parts.length; i < len; i++) { pos = parts[i].indexOf("="); if (pos < 0) { result[!i ? "defaultValue" : "i-" + i] = parts[i].trim(); } else { result[parts[i].substr(0, pos).trim().toLowerCase()] = parts[i].substr(pos + 1).trim(); } } return result; }; module.exports.mimeFunctions = { mimeEncode: function(str, toCharset, fromCharset) { toCharset = toCharset || "UTF-8"; fromCharset = fromCharset || "UTF-8"; var buffer = convert(str || "", toCharset, fromCharset), ranges = [ [0x09], [0x0A], [0x0D], [0x20], [0x21], [0x23, 0x3C], [0x3E], [0x40, 0x5E], [0x60, 0x7E] ], result = ""; for (var i = 0, len = buffer.length; i < len; i++) { if (checkRanges(buffer[i], ranges)) { result += String.fromCharCode(buffer[i]); continue; } result += "=" + (buffer[i] < 0x10 ? "0" : "") + buffer[i].toString(16).toUpperCase(); } return result; }, mimeDecode: function(str, toCharset, fromCharset) { str = (str || "").toString(); toCharset = toCharset || "UTF-8"; fromCharset = fromCharset || "UTF-8"; var encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length, bufferLength = str.length - encodedBytesCount * 2, chr, hex, buffer = new Buffer(bufferLength), bufferPos = 0; for (var i = 0, len = str.length; i < len; i++) { chr = str.charAt(i); if (chr == "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) { buffer[bufferPos++] = parseInt(hex, 16); i += 2; continue; } buffer[bufferPos++] = chr.charCodeAt(0); } if (fromCharset.toUpperCase().trim() == "BINARY") { return buffer; } return convert(buffer, toCharset, fromCharset); }, encodeBase64: function(str, toCharset, fromCharset) { var buffer = convert(str || "", toCharset, fromCharset); return addSoftLinebreaks(buffer.toString("base64"), "base64"); }, decodeBase64: function(str, toCharset, fromCharset) { var buffer = new Buffer((str || "").toString(), "base64"); return convert(buffer, toCharset, fromCharset); }, decodeQuotedPrintable: function(str, toCharset, fromCharset) { str = (str || "").toString(); str = str.replace(/\=(?:\r?\n|$)/g, ""); return this.mimeDecode(str, toCharset, fromCharset); }, encodeQuotedPrintable: function(str, toCharset, fromCharset) { var mimeEncodedStr = this.mimeEncode(str, toCharset, fromCharset); // fix line breaks mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() { return "\r\n"; }).replace(/[\t ]+$/gm, function(spaces) { return spaces.replace(/ /g, "=20").replace(/\t/g, "=09"); }); return addSoftLinebreaks(mimeEncodedStr, "qp"); }, encodeMimeWord: function(str, encoding, maxLength, toCharset, fromCharset) { toCharset = (toCharset || "utf-8").toString().toUpperCase().trim(); encoding = (encoding || "Q").toString().toUpperCase().trim().charAt(0); var encodedStr; if (maxLength && maxLength > 7 + toCharset.length) { maxLength -= (7 + toCharset.length); } if (encoding == "Q") { encodedStr = this.mimeEncode(str, toCharset, fromCharset); encodedStr = encodedStr.replace(/[\r\n\t_]/g, function(chr) { var code = chr.charCodeAt(0); return "=" + (code < 0x10 ? "0" : "") + code.toString(16).toUpperCase(); }).replace(/\s/g, "_"); } else if (encoding == "B") { encodedStr = convert(str || "", toCharset, fromCharset).toString("base64").trim(); } if (maxLength && encodedStr.length > maxLength) { if (encoding == "Q") { encodedStr = this.splitEncodedString(encodedStr, maxLength).join("?= =?" + toCharset + "?" + encoding + "?"); } else { encodedStr = encodedStr.replace(new RegExp(".{" + maxLength + "}", "g"), "$&?= =?" + toCharset + "?" + encoding + "?"); if (encodedStr.substr(-(" =?" + toCharset + "?" + encoding + "?=").length) == " =?" + toCharset + "?" + encoding + "?=") { encodedStr = encodedStr.substr(0, encodedStr.length - (" =?" + toCharset + "?" + encoding + "?=").length); } if (encodedStr.substr(-(" =?" + toCharset + "?" + encoding + "?").length) == " =?" + toCharset + "?" + encoding + "?") { encodedStr = encodedStr.substr(0, encodedStr.length - (" =?" + toCharset + "?" + encoding + "?").length); } } } return "=?" + toCharset + "?" + encoding + "?" + encodedStr + (encodedStr.substr(-2) == "?=" ? "" : "?="); }, decodeMimeWord: function(str, toCharset) { str = (str || "").toString().trim(); var fromCharset, encoding, match; match = str.match(/^\=\?([\w_\-]+)\?([QqBb])\?([^\?]*)\?\=$/i); if (!match) { return convert(str, toCharset); } fromCharset = match[1]; encoding = (match[2] || "Q").toString().toUpperCase(); str = (match[3] || "").replace(/_/g, " "); if (encoding == "B") { return this.decodeBase64(str, toCharset, fromCharset); } else if (encoding == "Q") { return this.mimeDecode(str, toCharset, fromCharset); } else { return str; } }, decodeMimeWords: function(str, toCharset) { var curCharset; str = (str || "").toString(). replace(/(=\?[^?]+\?[QqBb]\?[^?]+\?=)\s+(?==\?[^?]+\?[QqBb]\?[^?]*\?=)/g, "$1"). replace(/\=\?([\w_\-]+)\?([QqBb])\?[^\?]*\?\=/g, (function(mimeWord, charset, encoding) { curCharset = charset + encoding; return this.decodeMimeWord(mimeWord); }).bind(this)); return convert(str, toCharset); }, foldLine: function(str, lineLengthMax, afterSpace, lineMargin) { lineLengthMax = lineLengthMax || 76; lineMargin = lineMargin || Math.floor(lineLengthMax / 5); str = (str || "").toString().trim(); var pos = 0, len = str.length, result = "", line, match; while (pos < len) { line = str.substr(pos, lineLengthMax); if (line.length < lineLengthMax) { result += line; break; } if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) { line = match[0]; result += line; pos += line.length; continue; } else if ((match = line.substr(-lineMargin).match(/(\s+)[^\s]*$/))) { line = line.substr(0, line.length - (match[0].length - ( !! afterSpace ? (match[1] || "").length : 0))); } else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) { line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || "").length : 0)); } result += line; pos += line.length; if (pos < len) { result += "\r\n"; } } return result; }, encodeMimeWords: function(value, encoding, maxLength, toCharset, fromCharset) { var decodedValue = convert((value || ""), "utf-8", fromCharset).toString("utf-8"), encodedValue; encodedValue = decodedValue.replace(/([^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*(?:\s+[^\s\u0080-\uFFFF]*[\u0080-\uFFFF]+[^\s\u0080-\uFFFF]*\s*)?)+(?=\s|$)/g, (function(str) { return str.length ? this.encodeMimeWord(str, encoding || "Q", maxLength, toCharset) : ""; }).bind(this)); return encodedValue; }, encodeHeaderLine: function(key, value, toCharset, fromCharset) { var encodedValue = this.encodeMimeWords(value, 52, toCharset, fromCharset); return this.foldLine(key + ": " + encodedValue, 76); }, parseHeaderLines: function(headers, toCharset) { var lines = headers.split(/\r?\n|\r/), headersObj = {}, key, value, header, i, len; for (i = lines.length - 1; i >= 0; i--) { if (i && lines[i].match(/^\s/)) { lines[i - 1] += "\r\n" + lines[i]; lines.splice(i, 1); } } for (i = 0, len = lines.length; i < len; i++) { header = this.decodeHeaderLine(lines[i]); key = (header[0] || "").toString().toLowerCase().trim(); value = header[1] || ""; if (!toCharset || (toCharset || "").toString().trim().match(/^utf[\-_]?8$/i)) { value = value.toString("utf-8"); } if (!headersObj[key]) { headersObj[key] = [value]; } else { headersObj[key].push(value); } } return headersObj; }, decodeHeaderLine: function(header, toCharset) { var line = (header || "").toString().replace(/(?:\r?\n|\r)[ \t]*/g, " ").trim(), match = line.match(/^\s*([^:]+):(.*)$/), key = (match && match[1] || "").trim(), value = (match && match[2] || "").trim(); value = this.decodeMimeWords(value, toCharset); return [key, value]; }, splitEncodedString: function(str, maxlen) { var curLine, match, chr, done, lines = []; while (str.length) { curLine = str.substr(0, maxlen); // move incomplete escaped char back to main if ((match = curLine.match(/\=[0-9A-F]?$/i))) { curLine = curLine.substr(0, match.index); } done = false; while (!done) { done = true; // check if not middle of a unicode char sequence if ((match = str.substr(curLine.length).match(/^\=([0-9A-F]{2})/i))) { chr = parseInt(match[1], 16); // invalid sequence, move one char back anc recheck if (chr < 0xC2 && chr > 0x7F) { curLine = curLine.substr(0, curLine.length - 3); done = false; } } } if (curLine.length) { lines.push(curLine); } str = str.substr(curLine.length); } return lines; }, parseAddresses: addressparser }; // Lines can't be longer that 76 + <CR><LF> = 78 bytes // http://tools.ietf.org/html/rfc2045#section-6.7 function addSoftLinebreaks(str, encoding) { var lineLengthMax = 76; encoding = (encoding || "base64").toString().toLowerCase().trim(); if (encoding == "qp") { return addQPSoftLinebreaks(str, lineLengthMax); } else { return addBase64SoftLinebreaks(str, lineLengthMax); } } function addBase64SoftLinebreaks(base64EncodedStr, lineLengthMax) { base64EncodedStr = (base64EncodedStr || "").toString().trim(); return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim(); } function addQPSoftLinebreaks(mimeEncodedStr, lineLengthMax) { var pos = 0, len = mimeEncodedStr.length, match, code, line, lineMargin = Math.floor(lineLengthMax / 3), result = ""; // insert soft linebreaks where needed while (pos < len) { line = mimeEncodedStr.substr(pos, lineLengthMax); if ((match = line.match(/\r\n/))) { line = line.substr(0, match.index + match[0].length); result += line; pos += line.length; continue; } if (line.substr(-1) == "\n") { // nothing to change here result += line; pos += line.length; continue; } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) { // truncate to nearest line break line = line.substr(0, line.length - (match[0].length - 1)); result += line; pos += line.length; continue; } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) { // truncate to nearest space line = line.substr(0, line.length - (match[0].length - 1)); } else if (line.substr(-1) == "\r") { line = line.substr(0, line.length - 1); } else { if (line.match(/\=[\da-f]{0,2}$/i)) { // push incomplete encoding sequences to the next line if ((match = line.match(/\=[\da-f]{0,1}$/i))) { line = line.substr(0, line.length - match[0].length); } // ensure that utf-8 sequences are not split while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/ig))) { code = parseInt(match[0].substr(1, 2), 16); if (code < 128) { break; } line = line.substr(0, line.length - 3); if (code >= 0xC0) { break; } } } } if (pos + line.length < len && line.substr(-1) != "\n") { if (line.length == 76 && line.match(/\=[\da-f]{2}$/i)) { line = line.substr(0, line.length - 3); } else if (line.length == 76) { line = line.substr(0, line.length - 1); } pos += line.length; line += "=\r\n"; } else { pos += line.length; } result += line; } return result; } function checkRanges(nr, ranges) { for (var i = ranges.length - 1; i >= 0; i--) { if (!ranges[i].length) { continue; } if (ranges[i].length == 1 && nr == ranges[i][0]) { return true; } if (ranges[i].length == 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) { return true; } } return false; }