@mavogel/awscdk-rootmail 0.1.24 → 0.1.25
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/.jsii +3 -3
- package/lib/rootmail.js +1 -1
- package/lib/ses-receive.js +1 -1
- package/node_modules/@zone-eu/mailsplit/README.md +1 -1
- package/node_modules/@zone-eu/mailsplit/lib/headers.js +10 -4
- package/node_modules/@zone-eu/mailsplit/lib/message-splitter.js +5 -4
- package/node_modules/@zone-eu/mailsplit/lib/mime-node.d.ts +4 -1
- package/node_modules/@zone-eu/mailsplit/lib/mime-node.js +23 -2
- package/node_modules/@zone-eu/mailsplit/lib/types.d.ts +3 -1
- package/node_modules/@zone-eu/mailsplit/package.json +4 -4
- package/node_modules/libbase64/CHANGELOG.md +20 -0
- package/node_modules/libbase64/README.md +40 -13
- package/node_modules/libbase64/dist/cjs/libbase64.d.ts +80 -0
- package/node_modules/libbase64/dist/cjs/libbase64.js +310 -0
- package/node_modules/libbase64/dist/cjs/package.json +3 -0
- package/node_modules/libbase64/dist/esm/libbase64.d.ts +80 -0
- package/node_modules/libbase64/{lib → dist/esm}/libbase64.js +74 -117
- package/node_modules/libbase64/dist/esm/package.json +3 -0
- package/node_modules/libbase64/package.json +59 -13
- package/node_modules/libmime/CHANGELOG.md +31 -0
- package/node_modules/libmime/README.md +55 -26
- package/node_modules/libmime/SECURITY.md +152 -0
- package/node_modules/libmime/dist/cjs/charset.d.ts +43 -0
- package/node_modules/libmime/dist/cjs/charset.js +135 -0
- package/node_modules/libmime/dist/cjs/charsets.d.ts +2 -0
- package/node_modules/libmime/dist/cjs/charsets.js +213 -0
- package/node_modules/libmime/dist/cjs/get-charset-name.d.ts +1 -0
- package/node_modules/libmime/dist/cjs/get-charset-name.js +224 -0
- package/node_modules/libmime/dist/cjs/libmime.d.ts +218 -0
- package/node_modules/libmime/dist/cjs/libmime.js +1013 -0
- package/node_modules/libmime/dist/cjs/mimetypes.d.ts +7 -0
- package/node_modules/libmime/dist/cjs/mimetypes.js +2072 -0
- package/node_modules/libmime/dist/cjs/package.json +3 -0
- package/node_modules/libmime/dist/cjs/types.d.ts +59 -0
- package/node_modules/libmime/dist/cjs/types.js +4 -0
- package/node_modules/libmime/dist/esm/charset.d.ts +43 -0
- package/node_modules/libmime/dist/esm/charset.js +125 -0
- package/node_modules/libmime/dist/esm/charsets.d.ts +2 -0
- package/node_modules/libmime/{lib → dist/esm}/charsets.js +3 -5
- package/node_modules/libmime/dist/esm/get-charset-name.d.ts +1 -0
- package/node_modules/libmime/{lib → dist/esm}/get-charset-name.js +1 -8
- package/node_modules/libmime/dist/esm/libmime.d.ts +219 -0
- package/node_modules/libmime/{lib → dist/esm}/libmime.js +256 -329
- package/node_modules/libmime/dist/esm/mimetypes.d.ts +7 -0
- package/node_modules/libmime/dist/esm/mimetypes.js +2068 -0
- package/node_modules/libmime/dist/esm/package.json +3 -0
- package/node_modules/libmime/dist/esm/types.d.ts +59 -0
- package/node_modules/libmime/dist/esm/types.js +3 -0
- package/node_modules/libmime/package.json +58 -15
- package/node_modules/libqp/CHANGELOG.md +18 -0
- package/node_modules/libqp/README.md +50 -19
- package/node_modules/libqp/dist/cjs/libqp.d.ts +74 -0
- package/node_modules/libqp/dist/cjs/libqp.js +378 -0
- package/node_modules/libqp/dist/cjs/package.json +3 -0
- package/node_modules/libqp/dist/esm/libqp.d.ts +74 -0
- package/node_modules/libqp/dist/esm/libqp.js +369 -0
- package/node_modules/libqp/dist/esm/package.json +3 -0
- package/node_modules/libqp/lib/libqp.js +4 -383
- package/node_modules/libqp/lib/package.json +3 -0
- package/node_modules/libqp/package.json +62 -6
- package/node_modules/mailparser/.release-please-manifest.json +1 -1
- package/node_modules/mailparser/CHANGELOG.md +42 -0
- package/node_modules/mailparser/package.json +5 -5
- package/node_modules/nodemailer/CHANGELOG.md +44 -0
- package/node_modules/nodemailer/dist/cjs/addressparser/index.js +695 -267
- package/node_modules/nodemailer/dist/cjs/mailer/mail-message.js +4 -1
- package/node_modules/nodemailer/dist/cjs/mime-funcs/index.js +8 -2
- package/node_modules/nodemailer/dist/cjs/mime-node/index.js +48 -16
- package/node_modules/nodemailer/dist/cjs/package-info.d.ts +1 -1
- package/node_modules/nodemailer/dist/cjs/package-info.js +1 -1
- package/node_modules/nodemailer/dist/cjs/shared/url.js +22 -5
- package/node_modules/nodemailer/dist/esm/addressparser/index.js +695 -267
- package/node_modules/nodemailer/dist/esm/mailer/mail-message.js +4 -1
- package/node_modules/nodemailer/dist/esm/mime-funcs/index.js +8 -2
- package/node_modules/nodemailer/dist/esm/mime-node/index.js +48 -16
- package/node_modules/nodemailer/dist/esm/package-info.d.ts +1 -1
- package/node_modules/nodemailer/dist/esm/package-info.js +1 -1
- package/node_modules/nodemailer/dist/esm/shared/url.js +22 -5
- package/node_modules/nodemailer/package.json +1 -1
- package/package.json +2 -2
- package/node_modules/libbase64/.eslintrc +0 -11
- package/node_modules/libbase64/.github/FUNDING.yml +0 -4
- package/node_modules/libbase64/.github/VULNERABILITY_REPORT.yml +0 -75
- package/node_modules/libbase64/.github/workflows/release.yaml +0 -39
- package/node_modules/libbase64/.github/workflows/test.yml +0 -21
- package/node_modules/libbase64/.ncurc.js +0 -13
- package/node_modules/libbase64/.prettierrc.js +0 -8
- package/node_modules/libmime/lib/charset.js +0 -131
- package/node_modules/libmime/lib/mimetypes.js +0 -2049
- package/node_modules/libqp/.eslintrc +0 -15
- package/node_modules/libqp/.github/FUNDING.yml +0 -4
- package/node_modules/libqp/.github/VULNERABILITY_REPORT.yml +0 -75
- package/node_modules/libqp/.github/workflows/release.yaml +0 -39
- package/node_modules/libqp/.github/workflows/test.yml +0 -21
- package/node_modules/libqp/.prettierrc.js +0 -8
- package/node_modules/libqp/test/libqp-test.js +0 -366
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
name: "libqp security vulnerability report"
|
|
2
|
-
description: "Report an exploitable security vulnerability in libqp"
|
|
3
|
-
|
|
4
|
-
body:
|
|
5
|
-
- type: markdown
|
|
6
|
-
attributes:
|
|
7
|
-
value: |
|
|
8
|
-
Before reporting, read our
|
|
9
|
-
[Security Policy](https://github.com/nodemailer/libqp/security/policy).
|
|
10
|
-
|
|
11
|
-
Reports must demonstrate a credible security impact.
|
|
12
|
-
Technically valid bugs without a realistic attack
|
|
13
|
-
scenario belong in the regular issue tracker.
|
|
14
|
-
|
|
15
|
-
- type: textarea
|
|
16
|
-
id: summary
|
|
17
|
-
attributes:
|
|
18
|
-
label: Vulnerability summary
|
|
19
|
-
description: Describe the vulnerability and affected versions.
|
|
20
|
-
validations:
|
|
21
|
-
required: true
|
|
22
|
-
|
|
23
|
-
- type: textarea
|
|
24
|
-
id: attacker
|
|
25
|
-
attributes:
|
|
26
|
-
label: Attacker capabilities
|
|
27
|
-
description: |
|
|
28
|
-
What can an attacker control without already having
|
|
29
|
-
privileged access to the application or its configuration?
|
|
30
|
-
validations:
|
|
31
|
-
required: true
|
|
32
|
-
min_length: 100
|
|
33
|
-
|
|
34
|
-
- type: textarea
|
|
35
|
-
id: exploit
|
|
36
|
-
attributes:
|
|
37
|
-
label: Realistic exploitation scenario
|
|
38
|
-
description: |
|
|
39
|
-
Describe how this can be exploited in a real application.
|
|
40
|
-
Explain the prerequisites and security boundary crossed.
|
|
41
|
-
validations:
|
|
42
|
-
required: true
|
|
43
|
-
min_length: 150
|
|
44
|
-
|
|
45
|
-
- type: textarea
|
|
46
|
-
id: poc
|
|
47
|
-
attributes:
|
|
48
|
-
label: Proof of concept
|
|
49
|
-
description: Provide reproducible steps or working code.
|
|
50
|
-
validations:
|
|
51
|
-
required: true
|
|
52
|
-
|
|
53
|
-
- type: textarea
|
|
54
|
-
id: impact
|
|
55
|
-
attributes:
|
|
56
|
-
label: Security impact
|
|
57
|
-
description: |
|
|
58
|
-
What can the attacker achieve? Who is affected?
|
|
59
|
-
Explain why this is a security vulnerability rather
|
|
60
|
-
than an ordinary bug.
|
|
61
|
-
validations:
|
|
62
|
-
required: true
|
|
63
|
-
min_length: 100
|
|
64
|
-
|
|
65
|
-
- type: checkboxes
|
|
66
|
-
id: confirmation
|
|
67
|
-
attributes:
|
|
68
|
-
label: Reporting requirements
|
|
69
|
-
options:
|
|
70
|
-
- label: I have read the Security Policy.
|
|
71
|
-
required: true
|
|
72
|
-
- label: I have verified the finding against a supported version.
|
|
73
|
-
required: true
|
|
74
|
-
- label: I have described a credible attacker-controlled scenario.
|
|
75
|
-
required: true
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
on:
|
|
2
|
-
push:
|
|
3
|
-
branches:
|
|
4
|
-
- master
|
|
5
|
-
# Manual run: publish the version currently on master (used when a release was cut
|
|
6
|
-
# but the publish step failed)
|
|
7
|
-
workflow_dispatch:
|
|
8
|
-
|
|
9
|
-
permissions:
|
|
10
|
-
contents: write
|
|
11
|
-
pull-requests: write
|
|
12
|
-
id-token: write
|
|
13
|
-
|
|
14
|
-
name: release
|
|
15
|
-
jobs:
|
|
16
|
-
release-please:
|
|
17
|
-
runs-on: ubuntu-latest
|
|
18
|
-
steps:
|
|
19
|
-
- uses: google-github-actions/release-please-action@v3
|
|
20
|
-
id: release
|
|
21
|
-
if: ${{ github.event_name == 'push' }}
|
|
22
|
-
with:
|
|
23
|
-
release-type: node
|
|
24
|
-
package-name: ${{vars.NPM_MODULE_NAME}}
|
|
25
|
-
pull-request-title-pattern: 'chore${scope}: release ${version} [skip-ci]'
|
|
26
|
-
# The logic below handles the npm publication through npm trusted publishing
|
|
27
|
-
# (OIDC, no token secret): it runs when a new release is created, or on a
|
|
28
|
-
# manual dispatch.
|
|
29
|
-
- uses: actions/checkout@v4
|
|
30
|
-
if: ${{ steps.release.outputs.release_created || github.event_name == 'workflow_dispatch' }}
|
|
31
|
-
- uses: actions/setup-node@v4
|
|
32
|
-
with:
|
|
33
|
-
node-version: 24
|
|
34
|
-
registry-url: 'https://registry.npmjs.org'
|
|
35
|
-
if: ${{ steps.release.outputs.release_created || github.event_name == 'workflow_dispatch' }}
|
|
36
|
-
- run: npm ci
|
|
37
|
-
if: ${{ steps.release.outputs.release_created || github.event_name == 'workflow_dispatch' }}
|
|
38
|
-
- run: npm publish --provenance --access public
|
|
39
|
-
if: ${{ steps.release.outputs.release_created || github.event_name == 'workflow_dispatch' }}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: Run tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
pull_request:
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
strategy:
|
|
10
|
-
matrix:
|
|
11
|
-
node: [16.x, 18.x, 20.x, 21.x]
|
|
12
|
-
os: [ubuntu-latest]
|
|
13
|
-
runs-on: ${{ matrix.os }}
|
|
14
|
-
steps:
|
|
15
|
-
- uses: actions/checkout@v2
|
|
16
|
-
- name: Use Node.js ${{ matrix.node }}
|
|
17
|
-
uses: actions/setup-node@v4
|
|
18
|
-
with:
|
|
19
|
-
node-version: ${{ matrix.node }}
|
|
20
|
-
- run: npm install
|
|
21
|
-
- run: npm test
|
|
@@ -1,366 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { Buffer } = require('node:buffer');
|
|
4
|
-
const test = require('node:test');
|
|
5
|
-
const assert = require('node:assert').strict;
|
|
6
|
-
|
|
7
|
-
const libqp = require('../lib/libqp');
|
|
8
|
-
|
|
9
|
-
test('Encoding tests', async t => {
|
|
10
|
-
await t.test('simple string', async () => {
|
|
11
|
-
const encoded = libqp.encode('tere jõgeva');
|
|
12
|
-
|
|
13
|
-
assert.strictEqual(encoded, 'tere j=C3=B5geva');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
await t.test('stream', async () => {
|
|
17
|
-
let input = 'tere jõgeva';
|
|
18
|
-
let encoder = new libqp.Encoder();
|
|
19
|
-
|
|
20
|
-
let encoded = await new Promise((resolve, reject) => {
|
|
21
|
-
let chunks = [];
|
|
22
|
-
encoder.on('readable', () => {
|
|
23
|
-
let chunk;
|
|
24
|
-
|
|
25
|
-
while ((chunk = encoder.read()) !== null) {
|
|
26
|
-
chunks.push(chunk);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
encoder.on('end', () => {
|
|
30
|
-
resolve(Buffer.concat(chunks).toString());
|
|
31
|
-
});
|
|
32
|
-
encoder.on('Error', err => {
|
|
33
|
-
reject(err);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
encoder.end(Buffer.from(input));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
assert.strictEqual(encoded, 'tere j=C3=B5geva');
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('Decoding tests', async t => {
|
|
44
|
-
// Example taken from RFC2045 section 6.7
|
|
45
|
-
const encoded = "Now's the time =\r\n" + 'for all folk to come=\r\n' + ' to the aid of their country.';
|
|
46
|
-
const expectedDecoded = "Now's the time for all folk to come to the aid of their country.";
|
|
47
|
-
|
|
48
|
-
await t.test('simple string', async () => {
|
|
49
|
-
const decoded = libqp.decode(encoded).toString();
|
|
50
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await t.test('stream', async () => {
|
|
54
|
-
const decoder = new libqp.Decoder();
|
|
55
|
-
|
|
56
|
-
const decoded = await new Promise((resolve, reject) => {
|
|
57
|
-
const chunks = [];
|
|
58
|
-
decoder.on('readable', () => {
|
|
59
|
-
let chunk;
|
|
60
|
-
|
|
61
|
-
while ((chunk = decoder.read()) !== null) {
|
|
62
|
-
chunks.push(chunk);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
decoder.on('end', () => {
|
|
66
|
-
resolve(Buffer.concat(chunks).toString());
|
|
67
|
-
});
|
|
68
|
-
decoder.on('Error', err => {
|
|
69
|
-
reject(err);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
decoder.end(Buffer.from(encoded));
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
await t.test('stream, multiple chunks', async () => {
|
|
79
|
-
const encodedChunk1Length = 3;
|
|
80
|
-
const encodedChunk1 = encoded.substring(0, encodedChunk1Length);
|
|
81
|
-
const encodedChunk2 = encoded.substring(encodedChunk1Length);
|
|
82
|
-
|
|
83
|
-
const decoder = new libqp.Decoder();
|
|
84
|
-
|
|
85
|
-
const decoded = await new Promise((resolve, reject) => {
|
|
86
|
-
const chunks = [];
|
|
87
|
-
decoder.on('readable', () => {
|
|
88
|
-
let chunk;
|
|
89
|
-
|
|
90
|
-
while ((chunk = decoder.read()) !== null) {
|
|
91
|
-
chunks.push(chunk);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
decoder.on('end', () => {
|
|
95
|
-
resolve(Buffer.concat(chunks).toString());
|
|
96
|
-
});
|
|
97
|
-
decoder.on('Error', err => {
|
|
98
|
-
reject(err);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
decoder.write(Buffer.from(encodedChunk1));
|
|
102
|
-
decoder.end(Buffer.from(encodedChunk2));
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
await t.test('stream, space at end of chunk', async () => {
|
|
109
|
-
const encodedChunk1Length = encoded.indexOf(' ') + 1;
|
|
110
|
-
const encodedChunk1 = encoded.substring(0, encodedChunk1Length);
|
|
111
|
-
const encodedChunk2 = encoded.substring(encodedChunk1Length);
|
|
112
|
-
|
|
113
|
-
const decoder = new libqp.Decoder();
|
|
114
|
-
|
|
115
|
-
const decoded = await new Promise((resolve, reject) => {
|
|
116
|
-
const chunks = [];
|
|
117
|
-
decoder.on('readable', () => {
|
|
118
|
-
let chunk;
|
|
119
|
-
|
|
120
|
-
while ((chunk = decoder.read()) !== null) {
|
|
121
|
-
chunks.push(chunk);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
decoder.on('end', () => {
|
|
125
|
-
resolve(Buffer.concat(chunks).toString());
|
|
126
|
-
});
|
|
127
|
-
decoder.on('Error', err => {
|
|
128
|
-
reject(err);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
decoder.write(Buffer.from(encodedChunk1));
|
|
132
|
-
decoder.end(Buffer.from(encodedChunk2));
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
await t.test('stream, soft line break equals sign at end of chunk', async () => {
|
|
139
|
-
const encodedChunk1Length = encoded.indexOf('=') + 1;
|
|
140
|
-
const encodedChunk1 = encoded.substring(0, encodedChunk1Length);
|
|
141
|
-
const encodedChunk2 = encoded.substring(encodedChunk1Length);
|
|
142
|
-
|
|
143
|
-
const decoder = new libqp.Decoder();
|
|
144
|
-
|
|
145
|
-
const decoded = await new Promise((resolve, reject) => {
|
|
146
|
-
const chunks = [];
|
|
147
|
-
decoder.on('readable', () => {
|
|
148
|
-
let chunk;
|
|
149
|
-
|
|
150
|
-
while ((chunk = decoder.read()) !== null) {
|
|
151
|
-
chunks.push(chunk);
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
decoder.on('end', () => {
|
|
155
|
-
resolve(Buffer.concat(chunks).toString());
|
|
156
|
-
});
|
|
157
|
-
decoder.on('Error', err => {
|
|
158
|
-
reject(err);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
decoder.write(Buffer.from(encodedChunk1));
|
|
162
|
-
decoder.end(Buffer.from(encodedChunk2));
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
await t.test('stream, CR at end of chunk', async () => {
|
|
169
|
-
const encodedChunk1Length = encoded.indexOf('\r') + 1;
|
|
170
|
-
const encodedChunk1 = encoded.substring(0, encodedChunk1Length);
|
|
171
|
-
const encodedChunk2 = encoded.substring(encodedChunk1Length);
|
|
172
|
-
|
|
173
|
-
const decoder = new libqp.Decoder();
|
|
174
|
-
|
|
175
|
-
const decoded = await new Promise((resolve, reject) => {
|
|
176
|
-
const chunks = [];
|
|
177
|
-
decoder.on('readable', () => {
|
|
178
|
-
let chunk;
|
|
179
|
-
|
|
180
|
-
while ((chunk = decoder.read()) !== null) {
|
|
181
|
-
chunks.push(chunk);
|
|
182
|
-
}
|
|
183
|
-
});
|
|
184
|
-
decoder.on('end', () => {
|
|
185
|
-
resolve(Buffer.concat(chunks).toString());
|
|
186
|
-
});
|
|
187
|
-
decoder.on('Error', err => {
|
|
188
|
-
reject(err);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
decoder.write(Buffer.from(encodedChunk1));
|
|
192
|
-
decoder.end(Buffer.from(encodedChunk2));
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
assert.strictEqual(decoded, expectedDecoded);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
test('Raw 8-bit bytes in Quoted-Printable input', async t => {
|
|
200
|
-
const decodeStream = async chunks =>
|
|
201
|
-
await new Promise((resolve, reject) => {
|
|
202
|
-
const decoder = new libqp.Decoder();
|
|
203
|
-
const output = [];
|
|
204
|
-
decoder.on('data', chunk => output.push(chunk));
|
|
205
|
-
decoder.on('end', () => resolve(Buffer.concat(output)));
|
|
206
|
-
decoder.on('error', reject);
|
|
207
|
-
for (const chunk of chunks) {
|
|
208
|
-
decoder.write(chunk);
|
|
209
|
-
}
|
|
210
|
-
decoder.end();
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// "caf\xC3\xA9 \xE2\x82\xAC =C3=A9", raw UTF-8 bytes mixed with an escape
|
|
214
|
-
const utf8Input = Buffer.concat([Buffer.from('caf'), Buffer.from([0xc3, 0xa9, 0x20, 0xe2, 0x82, 0xac]), Buffer.from(' =C3=A9')]);
|
|
215
|
-
const utf8Expected = Buffer.from('café € é');
|
|
216
|
-
|
|
217
|
-
// "caf\xE9 =E9", a raw latin1 byte next to the same byte escaped
|
|
218
|
-
const latin1Input = Buffer.concat([Buffer.from('caf'), Buffer.from([0xe9]), Buffer.from(' =E9')]);
|
|
219
|
-
const latin1Expected = Buffer.from([0x63, 0x61, 0x66, 0xe9, 0x20, 0xe9]);
|
|
220
|
-
|
|
221
|
-
await t.test('decode() keeps raw UTF-8 bytes from a Buffer', async () => {
|
|
222
|
-
assert.deepStrictEqual(libqp.decode(utf8Input), utf8Expected);
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
await t.test('decode() keeps raw latin1 bytes from a Buffer', async () => {
|
|
226
|
-
assert.deepStrictEqual(libqp.decode(latin1Input), latin1Expected);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
await t.test('decode() of a binary string still maps char codes to bytes', async () => {
|
|
230
|
-
assert.deepStrictEqual(libqp.decode(latin1Input.toString('binary')), latin1Expected);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
await t.test('Decoder keeps raw UTF-8 bytes', async () => {
|
|
234
|
-
assert.deepStrictEqual(await decodeStream([utf8Input]), utf8Expected);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
await t.test('Decoder keeps raw latin1 bytes', async () => {
|
|
238
|
-
assert.deepStrictEqual(await decodeStream([latin1Input]), latin1Expected);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
await t.test('Decoder keeps raw bytes and escapes split byte by byte', async () => {
|
|
242
|
-
const chunks = [];
|
|
243
|
-
for (let i = 0; i < utf8Input.length; i++) {
|
|
244
|
-
chunks.push(utf8Input.subarray(i, i + 1));
|
|
245
|
-
}
|
|
246
|
-
assert.deepStrictEqual(await decodeStream(chunks), utf8Expected);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
await t.test('Decoder handles an escape split across chunks', async () => {
|
|
250
|
-
// split inside "=C3", once after "=" and once after "=C"
|
|
251
|
-
const input = Buffer.from('ab=C3=A9=\r\ncd');
|
|
252
|
-
const expected = Buffer.from('abécd');
|
|
253
|
-
assert.deepStrictEqual(await decodeStream([input.subarray(0, 3), input.subarray(3)]), expected);
|
|
254
|
-
assert.deepStrictEqual(await decodeStream([input.subarray(0, 4), input.subarray(4)]), expected);
|
|
255
|
-
// soft line break split between "=" and CRLF
|
|
256
|
-
assert.deepStrictEqual(await decodeStream([input.subarray(0, 9), input.subarray(9)]), expected);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test('Hyphens at the start of a line', async t => {
|
|
261
|
-
const encodeStream = async (chunks, options) =>
|
|
262
|
-
await new Promise((resolve, reject) => {
|
|
263
|
-
const encoder = new libqp.Encoder(options);
|
|
264
|
-
const output = [];
|
|
265
|
-
encoder.on('data', chunk => output.push(chunk));
|
|
266
|
-
encoder.on('end', () => resolve(Buffer.concat(output).toString()));
|
|
267
|
-
encoder.on('error', reject);
|
|
268
|
-
for (const chunk of chunks) {
|
|
269
|
-
encoder.write(chunk);
|
|
270
|
-
}
|
|
271
|
-
encoder.end();
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
await t.test('encode() escapes a hyphen that starts a line', async () => {
|
|
275
|
-
// two hyphens at the start of a line would read as a MIME boundary delimiter
|
|
276
|
-
assert.strictEqual(libqp.encode('--boundary\r\n-x'), '=2D-boundary\r\n=2Dx');
|
|
277
|
-
assert.strictEqual(libqp.encode('\n-'), '\n=2D');
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
await t.test('encode() keeps a hyphen inside a line', async () => {
|
|
281
|
-
assert.strictEqual(libqp.encode('a-b --c'), 'a-b --c');
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
await t.test('wrap() escapes a hyphen that a soft line break would move to the start of a line', async () => {
|
|
285
|
-
const input = 'a'.repeat(75) + '-x';
|
|
286
|
-
const wrapped = libqp.wrap(libqp.encode(input));
|
|
287
|
-
assert.strictEqual(wrapped, 'a'.repeat(75) + '=\r\n=2Dx');
|
|
288
|
-
assert.strictEqual(libqp.decode(wrapped).toString(), input);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
await t.test('wrap() keeps every line within the line length when escaping', async () => {
|
|
292
|
-
const input = '-'.repeat(300);
|
|
293
|
-
const wrapped = libqp.wrap(libqp.encode(input));
|
|
294
|
-
const lines = wrapped.split('\r\n');
|
|
295
|
-
assert.ok(lines.length > 3);
|
|
296
|
-
for (const line of lines) {
|
|
297
|
-
assert.ok(line.length <= 76, `line too long: ${line.length}`);
|
|
298
|
-
assert.notStrictEqual(line.charAt(0), '-');
|
|
299
|
-
}
|
|
300
|
-
assert.strictEqual(libqp.decode(wrapped).toString(), input);
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
await t.test('Encoder escapes a hyphen at the start of a chunk only when it starts a line', async () => {
|
|
304
|
-
for (const options of [{}, { lineLength: false }]) {
|
|
305
|
-
assert.strictEqual(await encodeStream(['-a'], options), '=2Da');
|
|
306
|
-
assert.strictEqual(await encodeStream(['ab\n', '-c'], options), 'ab\n=2Dc');
|
|
307
|
-
assert.strictEqual(await encodeStream(['ab', '-c'], options), 'ab-c');
|
|
308
|
-
}
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
await t.test('Encoder output never starts a line with a hyphen', async () => {
|
|
312
|
-
// a body that decodes to a boundary delimiter followed by the headers of a part
|
|
313
|
-
const input = 'text\r\n--boundary\r\nContent-Type: application/x-evil; name=evil.exe\r\n\r\n' + '-'.repeat(200) + '\r\n';
|
|
314
|
-
const chunks = [];
|
|
315
|
-
for (let i = 0; i < input.length; i += 7) {
|
|
316
|
-
chunks.push(Buffer.from(input.substring(i, i + 7)));
|
|
317
|
-
}
|
|
318
|
-
for (const options of [{}, { lineLength: false }]) {
|
|
319
|
-
const encoded = await encodeStream(chunks, options);
|
|
320
|
-
assert.ok(!/(^|\n)-/.test(encoded), `line starts with a hyphen: ${JSON.stringify(encoded)}`);
|
|
321
|
-
assert.strictEqual(libqp.decode(encoded).toString(), input);
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test('Trailing whitespace', async t => {
|
|
327
|
-
await t.test('decode() removes whitespace from the end of lines', async () => {
|
|
328
|
-
assert.strictEqual(libqp.decode('abc \r\ndef \t\nghi ').toString(), 'abc\r\ndef\nghi');
|
|
329
|
-
assert.strictEqual(libqp.decode(' \n ').toString(), '\n');
|
|
330
|
-
assert.strictEqual(libqp.decode('a b').toString(), 'a b');
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
await t.test('decode() treats a soft line break with trailing whitespace as a soft line break', async () => {
|
|
334
|
-
assert.strictEqual(libqp.decode('foo= \t\r\nbar').toString(), 'foobar');
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
await t.test('decode() handles a long run of whitespace in linear time', async () => {
|
|
338
|
-
// the trim used to be quadratic on a run that does not end the line, 50 KB took 2 seconds
|
|
339
|
-
for (const chr of [' ', '\t']) {
|
|
340
|
-
const input = chr.repeat(500 * 1024) + 'x';
|
|
341
|
-
const start = Date.now();
|
|
342
|
-
const decoded = libqp.decode(input).toString('binary');
|
|
343
|
-
const duration = Date.now() - start;
|
|
344
|
-
assert.strictEqual(decoded, input);
|
|
345
|
-
assert.ok(duration < 500, `decoding took ${duration} ms`);
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test('Escape sequences', async t => {
|
|
351
|
-
await t.test('decode() decodes valid escapes and keeps invalid ones', async () => {
|
|
352
|
-
assert.strictEqual(libqp.decode('=41=42=43').toString(), 'ABC');
|
|
353
|
-
assert.strictEqual(libqp.decode('=c3=a9').toString(), 'é');
|
|
354
|
-
assert.strictEqual(libqp.decode('=4G=4').toString(), '=4G=4');
|
|
355
|
-
assert.strictEqual(libqp.decode('==41').toString(), '=A');
|
|
356
|
-
assert.strictEqual(libqp.decode('a=\r\nb=\nc=').toString(), 'abc');
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
await t.test('decode() sizes the output for a body made of escapes', async () => {
|
|
360
|
-
const count = 1024 * 1024;
|
|
361
|
-
const decoded = libqp.decode('=41'.repeat(count));
|
|
362
|
-
assert.strictEqual(decoded.length, count);
|
|
363
|
-
assert.strictEqual(decoded[0], 0x41);
|
|
364
|
-
assert.strictEqual(decoded[count - 1], 0x41);
|
|
365
|
-
});
|
|
366
|
-
});
|