QuotedPrintable.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /** @license
  2. ========================================================================
  3. mimelib: http://github.com/andris9/mimelib
  4. Copyright (c) 2011-2012 Andris Reinman
  5. Permission is hereby granted, free of charge, to any person obtaining a copy
  6. of this software and associated documentation files (the "Software"), to deal
  7. in the Software without restriction, including without limitation the rights
  8. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. copies of the Software, and to permit persons to whom the Software is
  10. furnished to do so, subject to the following conditions:
  11. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  17. SOFTWARE.
  18. */
  19. /**
  20. * Quoted Printable operations.
  21. * Some parts taken from mimelib (http://github.com/andris9/mimelib)
  22. *
  23. * @author Andris Reinman
  24. * @author n1474335 [n1474335@gmail.com]
  25. * @copyright Crown Copyright 2016
  26. * @license Apache-2.0
  27. *
  28. * @namespace
  29. */
  30. var QuotedPrintable = {
  31. /**
  32. * To Quoted Printable operation.
  33. *
  34. * @param {byte_array} input
  35. * @param {Object[]} args
  36. * @returns {string}
  37. */
  38. run_to: function (input, args) {
  39. var mimeEncodedStr = QuotedPrintable.mimeEncode(input);
  40. // fix line breaks
  41. mimeEncodedStr = mimeEncodedStr.replace(/\r?\n|\r/g, function() {
  42. return "\r\n";
  43. }).replace(/[\t ]+$/gm, function(spaces) {
  44. return spaces.replace(/ /g, "=20").replace(/\t/g, "=09");
  45. });
  46. return QuotedPrintable._addSoftLinebreaks(mimeEncodedStr, "qp");
  47. },
  48. /**
  49. * From Quoted Printable operation.
  50. *
  51. * @param {string} input
  52. * @param {Object[]} args
  53. * @returns {byte_array}
  54. */
  55. run_from: function (input, args) {
  56. var str = input.replace(/\=(?:\r?\n|$)/g, "");
  57. return QuotedPrintable.mimeDecode(str);
  58. },
  59. /**
  60. * Decodes mime-encoded data.
  61. *
  62. * @param {string} str
  63. * @returns {byte_array}
  64. */
  65. mimeDecode: function(str) {
  66. var encodedBytesCount = (str.match(/\=[\da-fA-F]{2}/g) || []).length,
  67. bufferLength = str.length - encodedBytesCount * 2,
  68. chr, hex,
  69. buffer = new Array(bufferLength),
  70. bufferPos = 0;
  71. for (var i = 0, len = str.length; i < len; i++) {
  72. chr = str.charAt(i);
  73. if (chr === "=" && (hex = str.substr(i + 1, 2)) && /[\da-fA-F]{2}/.test(hex)) {
  74. buffer[bufferPos++] = parseInt(hex, 16);
  75. i += 2;
  76. continue;
  77. }
  78. buffer[bufferPos++] = chr.charCodeAt(0);
  79. }
  80. return buffer;
  81. },
  82. /**
  83. * Encodes mime data.
  84. *
  85. * @param {byte_array} buffer
  86. * @returns {string}
  87. */
  88. mimeEncode: function(buffer) {
  89. var ranges = [
  90. [0x09],
  91. [0x0A],
  92. [0x0D],
  93. [0x20],
  94. [0x21],
  95. [0x23, 0x3C],
  96. [0x3E],
  97. [0x40, 0x5E],
  98. [0x60, 0x7E]
  99. ],
  100. result = "";
  101. for (var i = 0, len = buffer.length; i < len; i++) {
  102. if (this._checkRanges(buffer[i], ranges)) {
  103. result += String.fromCharCode(buffer[i]);
  104. continue;
  105. }
  106. result += "=" + (buffer[i] < 0x10 ? "0" : "") + buffer[i].toString(16).toUpperCase();
  107. }
  108. return result;
  109. },
  110. /**
  111. * Checks if a given number falls within a given set of ranges.
  112. *
  113. * @private
  114. * @param {number} nr
  115. * @param {byte_array[]} ranges
  116. * @returns {bolean}
  117. */
  118. _checkRanges: function(nr, ranges) {
  119. for (var i = ranges.length - 1; i >= 0; i--) {
  120. if (!ranges[i].length)
  121. continue;
  122. if (ranges[i].length === 1 && nr === ranges[i][0])
  123. return true;
  124. if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1])
  125. return true;
  126. }
  127. return false;
  128. },
  129. /**
  130. * Adds soft line breaks to a string.
  131. * Lines can't be longer that 76 + <CR><LF> = 78 bytes
  132. * http://tools.ietf.org/html/rfc2045#section-6.7
  133. *
  134. * @private
  135. * @param {string} str
  136. * @param {string} encoding
  137. * @returns {string}
  138. */
  139. _addSoftLinebreaks: function(str, encoding) {
  140. var lineLengthMax = 76;
  141. encoding = (encoding || "base64").toString().toLowerCase().trim();
  142. if (encoding === "qp") {
  143. return this._addQPSoftLinebreaks(str, lineLengthMax);
  144. } else {
  145. return this._addBase64SoftLinebreaks(str, lineLengthMax);
  146. }
  147. },
  148. /**
  149. * Adds soft line breaks to a base64 string.
  150. *
  151. * @private
  152. * @param {string} base64EncodedStr
  153. * @param {number} lineLengthMax
  154. * @returns {string}
  155. */
  156. _addBase64SoftLinebreaks: function(base64EncodedStr, lineLengthMax) {
  157. base64EncodedStr = (base64EncodedStr || "").toString().trim();
  158. return base64EncodedStr.replace(new RegExp(".{" + lineLengthMax + "}", "g"), "$&\r\n").trim();
  159. },
  160. /**
  161. * Adds soft line breaks to a quoted printable string.
  162. *
  163. * @private
  164. * @param {string} mimeEncodedStr
  165. * @param {number} lineLengthMax
  166. * @returns {string}
  167. */
  168. _addQPSoftLinebreaks: function(mimeEncodedStr, lineLengthMax) {
  169. var pos = 0,
  170. len = mimeEncodedStr.length,
  171. match, code, line,
  172. lineMargin = Math.floor(lineLengthMax / 3),
  173. result = "";
  174. // insert soft linebreaks where needed
  175. while (pos < len) {
  176. line = mimeEncodedStr.substr(pos, lineLengthMax);
  177. if ((match = line.match(/\r\n/))) {
  178. line = line.substr(0, match.index + match[0].length);
  179. result += line;
  180. pos += line.length;
  181. continue;
  182. }
  183. if (line.substr(-1) === "\n") {
  184. // nothing to change here
  185. result += line;
  186. pos += line.length;
  187. continue;
  188. } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
  189. // truncate to nearest line break
  190. line = line.substr(0, line.length - (match[0].length - 1));
  191. result += line;
  192. pos += line.length;
  193. continue;
  194. } else if (line.length > lineLengthMax - lineMargin && (match = line.substr(-lineMargin).match(/[ \t\.,!\?][^ \t\.,!\?]*$/))) {
  195. // truncate to nearest space
  196. line = line.substr(0, line.length - (match[0].length - 1));
  197. } else if (line.substr(-1) === "\r") {
  198. line = line.substr(0, line.length - 1);
  199. } else {
  200. if (line.match(/\=[\da-f]{0,2}$/i)) {
  201. // push incomplete encoding sequences to the next line
  202. if ((match = line.match(/\=[\da-f]{0,1}$/i))) {
  203. line = line.substr(0, line.length - match[0].length);
  204. }
  205. // ensure that utf-8 sequences are not split
  206. while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/\=[\da-f]{2}$/ig))) {
  207. code = parseInt(match[0].substr(1, 2), 16);
  208. if (code < 128) {
  209. break;
  210. }
  211. line = line.substr(0, line.length - 3);
  212. if (code >= 0xC0) {
  213. break;
  214. }
  215. }
  216. }
  217. }
  218. if (pos + line.length < len && line.substr(-1) !== "\n") {
  219. if (line.length === 76 && line.match(/\=[\da-f]{2}$/i)) {
  220. line = line.substr(0, line.length - 3);
  221. } else if (line.length === 76) {
  222. line = line.substr(0, line.length - 1);
  223. }
  224. pos += line.length;
  225. line += "=\r\n";
  226. } else {
  227. pos += line.length;
  228. }
  229. result += line;
  230. }
  231. return result;
  232. },
  233. };