Permalink
Browse files

Updated mime word generation

  • Loading branch information...
1 parent a491b78 commit 62d4cab82ed4532c23812a9b9d3468e76e259efc Andris Reinman committed Sep 4, 2012
Showing with 119 additions and 10 deletions.
  1. +98 −9 lib/mimelib.js
  2. +1 −1 package.json
  3. +20 −0 test/mimelib.js
View
@@ -23,10 +23,24 @@ this.foldLine = function(str, maxLength, foldAnywhere, afterSpace){
* @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){
- return module.exports.mimeFunctions.encodeMimeWord(str, encoding, charset);
+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);
};
/**
@@ -237,11 +251,15 @@ module.exports.mimeFunctions = {
return addSoftLinebreaks(mimeEncodedStr, "qp");
},
- encodeMimeWord: function(str, encoding, toCharset, fromCharset){
+ 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){
@@ -252,7 +270,21 @@ module.exports.mimeFunctions = {
encodedStr = convert(str || "", toCharset, fromCharset).toString("base64").trim();
}
- return "=?"+toCharset+"?"+encoding+"?"+encodedStr+"?=";
+ 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){
@@ -281,10 +313,27 @@ module.exports.mimeFunctions = {
},
decodeMimeWords: function(str, toCharset){
+ var remainder = "", lastCharset, curCharset;
str = (str || "").toString();
- str = str.replace(/(\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)\s+(?=\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)/g,"$1").
- replace(/\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=/g, (function(mimeWord){
+ while(str.match(/(\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)\s+(?=\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)/g)){
+ str = str.replace(/(\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)\s+(\=\?[\w_\-]+\?[QB]\?[^\?]+\?\=)/g,function(original, first, second){
+ var match1 = (first || "").trim().match(/^\=\?([\w_\-]+)\?([QB])\?([^\?]+)\?\=$/),
+ match2 = (second || "").trim().match(/^\=\?([\w_\-]+)\?([QB])\?([^\?]+)\?\=$/);
+
+ if(match1[1] == match2[1] && match1[2] == match2[2]){
+ return "=?"+match1[1]+"?"+match1[2]+"?"+match1[3] + match2[3]+"?=";
+ }else{
+ return first+second;
+ }
+
+ });
+ }
+
+ str = str.replace(/\=\?([\w_\-]+)\?([QB])\?[^\?]+\?\=/g, (function(mimeWord, charset, encoding){
+
+ curCharset = charset + encoding;
+
return this.decodeMimeWord(mimeWord);
}).bind(this));
@@ -323,14 +372,19 @@ module.exports.mimeFunctions = {
return result;
},
- encodeHeaderLine: function(key, value, toCharset, fromCharset){
+ encodeMimeWords: function(value, encoding, maxLength, toCharset, fromCharset){
var decodedValue = convert((value || ""), "utf-8", fromCharset).toString("utf-8"),
encodedValue;
- encodedValue = decodedValue.replace(/\w*[\u0080-\uFFFF]+\w*(?:\s+\w*[\u0080-\uFFFF]+\w*)?/g, (function(str){
- return this.encodeMimeWord(str, "Q", toCharset);
+ encodedValue = decodedValue.replace(/(\w*[\u0080-\uFFFF]+\w*(?:\s+\w*[\u0080-\uFFFF]+\w*\s*)?)+/g, (function(str, o){
+ 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);
},
@@ -375,6 +429,41 @@ module.exports.mimeFunctions = {
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
};
View
@@ -1,7 +1,7 @@
{
"name": "mimelib",
"description": "MIME functions to encode/decode e-mails etc.",
- "version": "0.2.1",
+ "version": "0.2.2",
"author" : "Andris Reinman",
"homepage":"http://github.com/andris9/mimelib",
"maintainers":[
View
@@ -84,6 +84,26 @@ exports["Mime Words"] = {
"Parse Mime Words": function(test){
test.equal("Jõge-vaŽ zz Jõge-vaŽJõge-vaŽJõge-vaŽ", mimelib.parseMimeWords("=?ISO-8859-13?Q?J=F5ge-va=DE?= zz =?ISO-8859-13?Q?J=F5ge-va=DE?= =?ISO-8859-13?Q?J=F5ge-va=DE?= =?ISO-8859-13?Q?J=F5ge-va=DE?="))
test.done();
+ },
+
+ "Split on maxLength QP": function(test){
+ var inputStr = "Jõgeva Jõgeva Jõgeva mugeva Jõgeva Jõgeva Jõgeva Jõgeva Jõgeva",
+ outputStr = "=?ISO-8859-1?Q?J=F5geva_J=F5gev?= =?ISO-8859-1?Q?a_J=F5geva?= mugeva =?ISO-8859-1?Q?J=F5geva_J=F5gev?= =?ISO-8859-1?Q?a_J=F5geva_J=F5g?= =?ISO-8859-1?Q?eva_J=F5geva?=",
+ encoded = mimelib.encodeMimeWords(inputStr, "Q", 16, "ISO-8859-1");
+
+ test.equal(outputStr, encoded)
+ test.equal(inputStr, mimelib.parseMimeWords(encoded));
+ test.done();
+ },
+
+ "Split on maxLength Base64": function(test){
+ var inputStr = "Jõgeva Jõgeva Jõgeva mugeva Jõgeva Jõgeva Jõgeva Jõgeva Jõgeva",
+ outputStr = "=?ISO-8859-1?B?SvVnZXZhIEr1Z2V2?= =?ISO-8859-1?B?YSBK9WdldmE=?= mugeva =?ISO-8859-1?B?SvVnZXZhIEr1Z2V2?= =?ISO-8859-1?B?YSBK9WdldmEgSvVn?= =?ISO-8859-1?B?ZXZhIEr1Z2V2YQ==?=",
+ encoded = mimelib.encodeMimeWords(inputStr,"B", 16, "ISO-8859-1");
+
+ test.equal(outputStr, encoded)
+ test.equal(inputStr, mimelib.parseMimeWords(encoded));
+ test.done();
}
}

0 comments on commit 62d4cab

Please sign in to comment.