Skip to content

Commit

Permalink
v6.4.12
Browse files Browse the repository at this point in the history
  • Loading branch information
andris9 committed Sep 30, 2020
1 parent 1787f22 commit 455cfbe
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 68 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 6.4.12 2020-09-30

- Better handling of attadhment filenames that include quote symbols
- Includes all information from the oath2 error response in the error message (Normal Gaussian) [1787f227]

## 6.4.11 2020-07-29

- Fixed escape sequence handling in address parsing
Expand Down
31 changes: 11 additions & 20 deletions lib/mime-funcs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ module.exports = {
* @param {String} value String to be tested
* @returns {Boolean} true if it is a plaintext string
*/
isPlainText(value) {
if (typeof value !== 'string' || /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/.test(value)) {
isPlainText(value, isParam) {
const re = isParam ? /[\x00-\x08\x0b\x0c\x0e-\x1f"\u0080-\uFFFF]/ : /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/;
if (typeof value !== 'string' || re.test(value)) {
return false;
} else {
return true;
Expand Down Expand Up @@ -49,11 +50,7 @@ module.exports = {
* @return {String} Single or several mime words joined together
*/
encodeWord(data, mimeWordEncoding, maxLength) {
mimeWordEncoding = (mimeWordEncoding || 'Q')
.toString()
.toUpperCase()
.trim()
.charAt(0);
mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0);
maxLength = maxLength || 0;

let encodedStr;
Expand All @@ -66,10 +63,7 @@ module.exports = {
if (mimeWordEncoding === 'Q') {
// https://tools.ietf.org/html/rfc2047#section-5 rule (3)
encodedStr = qp.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, chr => {
let ord = chr
.charCodeAt(0)
.toString(16)
.toUpperCase();
let ord = chr.charCodeAt(0).toString(16).toUpperCase();
if (chr === ' ') {
return '_';
} else {
Expand Down Expand Up @@ -131,8 +125,8 @@ module.exports = {

let encodedValue;

// find first word with a non-printable ascii in it
let firstMatch = value.match(/(?:^|\s)([^\s]*[\u0080-\uFFFF])/);
// find first word with a non-printable ascii or special symbol in it
let firstMatch = value.match(/(?:^|\s)([^\s]*["\u0080-\uFFFF])/);
if (!firstMatch) {
return value;
}
Expand All @@ -144,7 +138,7 @@ module.exports = {
}

// find the last word with a non-printable ascii in it
let lastMatch = value.match(/([\u0080-\uFFFF][^\s]*)[^\u0080-\uFFFF]*$/);
let lastMatch = value.match(/(["\u0080-\uFFFF][^\s]*)[^"\u0080-\uFFFF]*$/);
if (!lastMatch) {
// should not happen
return value;
Expand Down Expand Up @@ -181,7 +175,7 @@ module.exports = {
// filename might include unicode characters so it is a special case
// other values probably do not
let value = structured.params[param];
if (!this.isPlainText(value) || value.length >= 75) {
if (!this.isPlainText(value, true) || value.length >= 75) {
this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
paramsArray.push(encodedParam.key + '=' + encodedParam.value);
Expand Down Expand Up @@ -226,7 +220,7 @@ module.exports = {
maxLength = maxLength || 50;

// process ascii only text
if (this.isPlainText(data)) {
if (this.isPlainText(data, true)) {
// check if conversion is even needed
if (encodedStr.length <= maxLength) {
return [
Expand Down Expand Up @@ -591,10 +585,7 @@ module.exports = {

encodeURICharComponent: chr => {
let res = '';
let ord = chr
.charCodeAt(0)
.toString(16)
.toUpperCase();
let ord = chr.charCodeAt(0).toString(16).toUpperCase();

if (ord.length % 2) {
ord = '0' + ord;
Expand Down
7 changes: 7 additions & 0 deletions lib/mime-node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ class MimeNode {
}
value = mimeFuncs.buildHeaderValue(structured);
break;

case 'Content-Type':
structured = mimeFuncs.parseHeaderValue(value);

Expand All @@ -554,6 +555,7 @@ class MimeNode {
value += '; name=' + param;
}
break;

case 'Bcc':
if (!this.keepBcc) {
// skip BCC values
Expand Down Expand Up @@ -1099,6 +1101,11 @@ class MimeNode {
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
return this._encodeWords(value);

case 'Content-Type':
case 'Content-Disposition':
// if it includes a filename then it is already encoded
return (value || '').toString().replace(/\r?\n|\r/g, ' ');

default:
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
// encodeWords only encodes if needed, otherwise the original string is returned
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nodemailer",
"version": "6.4.11",
"version": "6.4.12",
"description": "Easy as cake e-mail sending from your Node.js applications",
"main": "lib/nodemailer.js",
"scripts": {
Expand All @@ -24,19 +24,19 @@
"bunyan": "1.8.14",
"chai": "4.2.0",
"eslint-config-nodemailer": "1.2.0",
"eslint-config-prettier": "6.11.0",
"grunt": "1.2.1",
"eslint-config-prettier": "6.12.0",
"grunt": "1.3.0",
"grunt-cli": "1.3.2",
"grunt-eslint": "23.0.0",
"grunt-mocha-test": "0.13.3",
"libbase64": "1.2.1",
"libmime": "5.0.0",
"libqp": "1.1.0",
"mocha": "8.0.1",
"mocha": "8.1.3",
"nodemailer-ntlm-auth": "1.0.1",
"proxy": "1.0.2",
"proxy-test-server": "1.0.0",
"sinon": "9.0.2",
"sinon": "9.1.0",
"smtp-server": "3.7.0"
},
"engines": {
Expand Down
43 changes: 43 additions & 0 deletions test/mail-composer/mail-composer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,49 @@ describe('MailComposer unit tests', function () {
});
});

it('should encode filename', function (done) {
let data = {
text: 'abc',
baseBoundary: 'test',
messageId: 'zzzzzz',
date: 'Sat, 21 Jun 2014 10:52:44 +0000',
attachments: [
{
content: 'test',
filename: '"test" it is.txt'
}
]
};

let expected =
'' +
'Content-Type: multipart/mixed; boundary="--_NmP-test-Part_1"\r\n' +
'Message-ID: <zzzzzz>\r\n' +
'Date: Sat, 21 Jun 2014 10:52:44 +0000\r\n' +
'MIME-Version: 1.0\r\n' +
'\r\n' +
'----_NmP-test-Part_1\r\n' +
'Content-Type: text/plain; charset=utf-8\r\n' +
'Content-Transfer-Encoding: 7bit\r\n' +
'\r\n' +
'abc\r\n-' +
'---_NmP-test-Part_1\r\n' +
'Content-Type: text/plain; name="=?UTF-8?Q?=22test=22_it_is=2Etxt?="\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'Content-Disposition: attachment;\r\n' +
" filename*0*=utf-8''%22test%22%20it%20is.txt\r\n" +
'\r\n' +
'dGVzdA==\r\n' +
'----_NmP-test-Part_1--\r\n';

let mail = new MailComposer(data).compile();
mail.build(function (err, message) {
expect(err).to.not.exist;
expect(message.toString()).to.equal(expected);
done();
});
});

it('should keep plaintext for attachment', function (done) {
let data = {
text: 'abc',
Expand Down
Loading

0 comments on commit 455cfbe

Please sign in to comment.