libmime 0.1.6 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## v1.2.0 2015-10-05
4
+
5
+ Added support for emojis in header params (eg. filenames)
6
+
7
+ ## v1.1.0 2015-09-24
8
+
9
+ Updated encoded word encoding with quoted printable, should be more like required in https://tools.ietf.org/html/rfc2047#section-5
10
+
11
+ ## v1.0.0 2015-04-15
12
+
13
+ Changed versioning scheme to use 1.x instead of 0.x versions. Bumped dependency versions, no actual code changes.
14
+
15
+ ## v0.1.7 2015-01-19
16
+
17
+ Updated unicode filename handling – only revert to parameter continuation if the value actually includes
18
+ non-ascii characters or is too long. Previously filenames were encoded if they included anything
19
+ besides letters, numbers, dot or space.
20
+
3
21
  ## v0.1.6 2014-10-25
4
22
 
5
23
  Fixed an issue with `encodeWords` where a trailing space was invalidly included in a word if the word
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Andris Reinman
1
+ Copyright (c) 2014-2015 Andris Reinman
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -189,8 +189,8 @@ libmime.buildHeaderParam('filename', 'filename õäöü.txt', 20);
189
189
  This can be combined into a properly formatted header:
190
190
 
191
191
  ```
192
- Content-disposition: attachment; filename*0*="utf-8''filename%20"
193
- filename*1*="%C3%B5%C3%A4%C3%B6"; filename*2*="%C3%BC.txt"
192
+ Content-disposition: attachment; filename*0*=utf-8''filename%20
193
+ filename*1*=%C3%B5%C3%A4%C3%B6; filename*2*=%C3%BC.txt
194
194
  ```
195
195
 
196
196
  ### MIME Types
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "libmime",
3
3
  "description": "Encode and decode quoted printable and base64 strings",
4
- "version": "0.1.6",
4
+ "version": "1.2.0",
5
5
  "main": "src/libmime",
6
6
  "homepage": "https://github.com/andris9/libmime",
7
7
  "repository": {
@@ -19,14 +19,15 @@
19
19
  "test": "grunt"
20
20
  },
21
21
  "dependencies": {
22
- "iconv-lite": "^0.4.4",
22
+ "iconv-lite": "^0.4.13",
23
23
  "libbase64": "^0.1.0",
24
- "libqp": "^0.1.1"
24
+ "libqp": "^1.1.0"
25
25
  },
26
26
  "devDependencies": {
27
- "chai": "^1.9.1",
27
+ "chai": "^3.3.0",
28
28
  "grunt": "^0.4.5",
29
- "grunt-contrib-jshint": "^0.10.0",
30
- "grunt-mocha-test": "^0.11.0"
29
+ "grunt-contrib-jshint": "^0.11.3",
30
+ "grunt-mocha-test": "^0.12.7",
31
+ "mocha": "^2.3.3"
31
32
  }
32
33
  }
package/src/charset.js CHANGED
@@ -29,7 +29,7 @@ var charset = module.exports = {
29
29
  decode: function(buf, fromCharset) {
30
30
  fromCharset = charset.normalizeCharset(fromCharset || 'UTF-8');
31
31
 
32
- if(/^(us\-)?ascii|utf\-8|7bit$/i.test(fromCharset)){
32
+ if (/^(us\-)?ascii|utf\-8|7bit$/i.test(fromCharset)) {
33
33
  return buf.toString('utf-8');
34
34
  }
35
35
 
package/src/libmime.js CHANGED
@@ -48,23 +48,23 @@ var libmime = module.exports = {
48
48
 
49
49
  return str.
50
50
  split(/\r?\n/).
51
- // remove soft linebreaks
52
- // soft linebreaks are added after space symbols
51
+ // remove soft linebreaks
52
+ // soft linebreaks are added after space symbols
53
53
  reduce(function(previousValue, currentValue, index) {
54
- var body = previousValue;
55
- if (delSp) {
56
- // delsp adds spaces to text to be able to fold it
57
- // these spaces can be removed once the text is unfolded
58
- body = body.replace(/[ ]+$/, '');
59
- }
60
- if (/ $/.test(previousValue) && !/(^|\n)\-\- $/.test(previousValue) || index === 1) {
61
- return body + currentValue;
62
- } else {
63
- return body + '\n' + currentValue;
64
- }
65
- }).
66
- // remove whitespace stuffing
67
- // http://tools.ietf.org/html/rfc3676#section-4.4
54
+ var body = previousValue;
55
+ if (delSp) {
56
+ // delsp adds spaces to text to be able to fold it
57
+ // these spaces can be removed once the text is unfolded
58
+ body = body.replace(/[ ]+$/, '');
59
+ }
60
+ if (/ $/.test(previousValue) && !/(^|\n)\-\- $/.test(previousValue) || index === 1) {
61
+ return body + currentValue;
62
+ } else {
63
+ return body + '\n' + currentValue;
64
+ }
65
+ }).
66
+ // remove whitespace stuffing
67
+ // http://tools.ietf.org/html/rfc3676#section-4.4
68
68
  replace(/^ /gm, '');
69
69
  },
70
70
 
@@ -110,10 +110,15 @@ var libmime = module.exports = {
110
110
  }
111
111
 
112
112
  if (mimeWordEncoding === 'Q') {
113
- encodedStr = libqp.encode(data).replace(/[_?\r\n\t"]/g, function(chr) {
113
+ // https://tools.ietf.org/html/rfc2047#section-5 rule (3)
114
+ encodedStr = libqp.encode(data).replace(/[^a-z0-9!*+\-\/=]/ig, function(chr) {
114
115
  var ord = chr.charCodeAt(0).toString(16).toUpperCase();
115
- return '=' + (ord.length === 1 ? '0' + ord : ord);
116
- }).replace(/%20| /g, '_');
116
+ if (chr === ' ') {
117
+ return '_';
118
+ } else {
119
+ return '=' + (ord.length === 1 ? '0' + ord : ord);
120
+ }
121
+ });
117
122
  } else if (mimeWordEncoding === 'B') {
118
123
  encodedStr = typeof data === 'string' ? data : libbase64.encode(data);
119
124
  maxLength = Math.max(3, (maxLength - maxLength % 4) / 4 * 3);
@@ -284,17 +289,17 @@ var libmime = module.exports = {
284
289
  Object.keys(structured.params || {}).forEach(function(param) {
285
290
  // filename might include unicode characters so it is a special case
286
291
  var value = structured.params[param];
287
- if (param === 'filename' || !libmime.isPlainText(value) || value.length >= 75) {
292
+ if (!libmime.isPlainText(value) || value.length >= 75) {
288
293
  libmime.buildHeaderParam(param, value, 50).forEach(function(encodedParam) {
289
294
  if (!/[\s"\\;\/=]|^[\-']|'$/.test(encodedParam.value)) {
290
295
  paramsArray.push(encodedParam.key + '=' + encodedParam.value);
291
296
  } else {
292
- paramsArray.push(encodedParam.key + '="' + encodedParam.value + '"');
297
+ paramsArray.push(encodedParam.key + '=' + JSON.stringify(encodedParam.value));
293
298
  }
294
299
  });
295
300
  } else {
296
301
  if (/[\s'"\\;\/=]|^\-/.test(value)) {
297
- paramsArray.push(param + '="' + value.replace(/(["\\])/g, "\\$1") + '"');
302
+ paramsArray.push(param + '=' + JSON.stringify(value));
298
303
  } else {
299
304
  paramsArray.push(param + '=' + value);
300
305
  }
@@ -422,16 +427,16 @@ var libmime = module.exports = {
422
427
  response.params[key].charset +
423
428
  '?Q?' +
424
429
  value.
425
- // fix invalidly encoded chars
430
+ // fix invalidly encoded chars
426
431
  replace(/[=\?_\s]/g, function(s) {
427
- var c = s.charCodeAt(0).toString(16);
428
- if (s === ' ') {
429
- return '_';
430
- } else {
431
- return '%' + (c.length < 2 ? '0' : '') + c;
432
- }
433
- }).
434
- // change from urlencoding to percent encoding
432
+ var c = s.charCodeAt(0).toString(16);
433
+ if (s === ' ') {
434
+ return '_';
435
+ } else {
436
+ return '%' + (c.length < 2 ? '0' : '') + c;
437
+ }
438
+ }).
439
+ // change from urlencoding to percent encoding
435
440
  replace(/%/g, '=') +
436
441
  '?=';
437
442
  } else {
@@ -450,8 +455,8 @@ var libmime = module.exports = {
450
455
  * For example
451
456
  * title="unicode string"
452
457
  * becomes
453
- * title*0*="utf-8''unicode"
454
- * title*1*="%20string"
458
+ * title*0*=utf-8''unicode
459
+ * title*1*=%20string
455
460
  *
456
461
  * @param {String|Buffer} data String to be encoded
457
462
  * @param {Number} [maxLength=50] Max length for generated chunks
@@ -461,15 +466,17 @@ var libmime = module.exports = {
461
466
  buildHeaderParam: function(key, data, maxLength, fromCharset) {
462
467
  var list = [];
463
468
  var encodedStr = typeof data === 'string' ? data : libmime.decode(data, fromCharset);
464
- var chr;
469
+ var encodedStrArr;
470
+ var chr, ord;
465
471
  var line;
466
472
  var startPos = 0;
467
473
  var isEncoded = false;
474
+ var i, len;
468
475
 
469
476
  maxLength = maxLength || 50;
470
477
 
471
478
  // process ascii only text
472
- if (/^[\w.\- ]*$/.test(data)) {
479
+ if (libmime.isPlainText(data)) {
473
480
 
474
481
  // check if conversion is even needed
475
482
  if (encodedStr.length <= maxLength) {
@@ -494,21 +501,39 @@ var libmime = module.exports = {
494
501
 
495
502
  } else {
496
503
 
504
+ if (/[\uD800-\uDBFF]/.test(encodedStr)) {
505
+ // string containts surrogate pairs, so normalize it to an array of bytes
506
+ encodedStrArr = [];
507
+ for (i = 0, len = encodedStr.length; i < len; i++) {
508
+ chr = encodedStr.charAt(i);
509
+ ord = chr.charCodeAt(0);
510
+ if (ord >= 0xD800 && ord <= 0xDBFF && i < len - 1) {
511
+ chr += encodedStr.charAt(i + 1);
512
+ encodedStrArr.push(chr);
513
+ i++;
514
+ } else {
515
+ encodedStrArr.push(chr);
516
+ }
517
+ }
518
+ encodedStr = encodedStrArr;
519
+ }
520
+
497
521
  // first line includes the charset and language info and needs to be encoded
498
522
  // even if it does not contain any unicode characters
499
523
  line = 'utf-8\'\'';
500
524
  isEncoded = true;
501
525
  startPos = 0;
526
+
502
527
  // process text with unicode or special chars
503
- for (var i = 0, len = encodedStr.length; i < len; i++) {
528
+ for (i = 0, len = encodedStr.length; i < len; i++) {
504
529
 
505
530
  chr = encodedStr[i];
506
531
 
507
532
  if (isEncoded) {
508
- chr = encodeURIComponent(chr);
533
+ chr = safeEncodeURIComponent(chr);
509
534
  } else {
510
535
  // try to urlencode current char
511
- chr = chr === ' ' ? chr : encodeURIComponent(chr);
536
+ chr = chr === ' ' ? chr : safeEncodeURIComponent(chr);
512
537
  // By default it is not required to encode a line, the need
513
538
  // only appears when the string contains unicode or special chars
514
539
  // in this case we start processing the line over and encode all chars
@@ -516,7 +541,7 @@ var libmime = module.exports = {
516
541
  // Check if it is even possible to add the encoded char to the line
517
542
  // If not, there is no reason to use this line, just push it to the list
518
543
  // and start a new line with the char that needs encoding
519
- if ((encodeURIComponent(line) + chr).length >= maxLength) {
544
+ if ((safeEncodeURIComponent(line) + chr).length >= maxLength) {
520
545
  list.push({
521
546
  line: line,
522
547
  encoded: isEncoded
@@ -538,7 +563,7 @@ var libmime = module.exports = {
538
563
  line: line,
539
564
  encoded: isEncoded
540
565
  });
541
- line = chr = encodedStr[i] === ' ' ? ' ' : encodeURIComponent(encodedStr[i]);
566
+ line = chr = encodedStr[i] === ' ' ? ' ' : safeEncodeURIComponent(encodedStr[i]);
542
567
  if (chr === encodedStr[i]) {
543
568
  isEncoded = false;
544
569
  startPos = i - 1;
@@ -569,6 +594,7 @@ var libmime = module.exports = {
569
594
  });
570
595
  },
571
596
 
597
+
572
598
  /**
573
599
  * Returns file extension for a content type string. If no suitable extensions
574
600
  * are found, 'bin' is used as the default extension
@@ -720,4 +746,38 @@ function splitMimeEncodedString(str, maxlen) {
720
746
  }
721
747
 
722
748
  return lines;
749
+ }
750
+
751
+ function encodeURICharComponent(chr) {
752
+ var i, len, ord;
753
+ var res = '';
754
+
755
+ ord = chr.charCodeAt(0).toString(16).toUpperCase();
756
+ if (ord.length % 2) {
757
+ ord = '0' + ord;
758
+ }
759
+ if (ord.length > 2) {
760
+ for (i = 0, len = ord.length / 2; i < len; i++) {
761
+ res += '%' + ord.substr(i, 2);
762
+ }
763
+ } else {
764
+ res += '%' + ord;
765
+ }
766
+
767
+ return res;
768
+ }
769
+
770
+ function safeEncodeURIComponent(str) {
771
+ str = (str || '').toString();
772
+
773
+ try {
774
+ // might throw if we try to encode invalid sequences, eg. partial emoji
775
+ str = encodeURIComponent(str);
776
+ } catch (E) {
777
+ // should never run
778
+ return str.replace(/[^\x00-\x1F *'()<>@,;:\\"\[\]?=\u007F-\uFFFF]+/g, '');
779
+ }
780
+
781
+ // ensure chars that are not handled by encodeURICompent are converted as well
782
+ return str.replace(/[\x00-\x1F *'()<>@,;:\\"\[\]?=\u007F-\uFFFF]/g, encodeURICharComponent);
723
783
  }