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 +18 -0
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/package.json +7 -6
- package/src/charset.js +1 -1
- package/src/libmime.js +100 -40
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
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*=
|
|
193
|
-
filename*1
|
|
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": "
|
|
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.
|
|
22
|
+
"iconv-lite": "^0.4.13",
|
|
23
23
|
"libbase64": "^0.1.0",
|
|
24
|
-
"libqp": "^
|
|
24
|
+
"libqp": "^1.1.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"chai": "^
|
|
27
|
+
"chai": "^3.3.0",
|
|
28
28
|
"grunt": "^0.4.5",
|
|
29
|
-
"grunt-contrib-jshint": "^0.
|
|
30
|
-
"grunt-mocha-test": "^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
|
-
|
|
52
|
-
|
|
51
|
+
// remove soft linebreaks
|
|
52
|
+
// soft linebreaks are added after space symbols
|
|
53
53
|
reduce(function(previousValue, currentValue, index) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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 (
|
|
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 + '=
|
|
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 + '=
|
|
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
|
-
|
|
430
|
+
// fix invalidly encoded chars
|
|
426
431
|
replace(/[=\?_\s]/g, function(s) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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*=
|
|
454
|
-
* title*1
|
|
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
|
|
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 (
|
|
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 (
|
|
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 =
|
|
533
|
+
chr = safeEncodeURIComponent(chr);
|
|
509
534
|
} else {
|
|
510
535
|
// try to urlencode current char
|
|
511
|
-
chr = chr === ' ' ? 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 ((
|
|
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] === ' ' ? ' ' :
|
|
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
|
}
|