fix: stop crafted .eml headers from stalling the parser by moving to postal-mime 4.2

This commit is contained in:
Linus Rath
2026-10-31 16:02:01 +01:00
parent 7e60d32449
commit 368c90ce3e
6 changed files with 139 additions and 90 deletions
+8 -5
View File
@@ -34,7 +34,7 @@
"expo-status-bar": "~3.0.9",
"expo-web-browser": "~15.0.8",
"jszip": "^3.10.1",
"postal-mime": "^3.0.0",
"postal-mime": "^4.2.0",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-android-widget": "0.22.1",
@@ -8940,10 +8940,13 @@
}
},
"node_modules/postal-mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-3.0.0.tgz",
"integrity": "sha512-Z4a9ar2Bv3YpK3IXag+Yda30k7bMZfpRuUGyqtHnZ2pjHG8Bl62EhZIk4n1dzv00gfzP9g+94e9kd8+XmjVWLA==",
"license": "MIT-0"
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postal-mime/-/postal-mime-4.2.0.tgz",
"integrity": "sha512-QG5Rh8wY/zjwFF1lKWcBbwWcAWR5jWodmMPL4gMik43XDLebtWoAAPyG0lrE2kYYZ5IHTASrepGuGO0YIwNKAg==",
"license": "MIT-0",
"engines": {
"node": ">=20"
}
},
"node_modules/postcss": {
"version": "8.4.49",
+1 -1
View File
@@ -43,7 +43,7 @@
"expo-status-bar": "~3.0.9",
"expo-web-browser": "~15.0.8",
"jszip": "^3.10.1",
"postal-mime": "^3.0.0",
"postal-mime": "^4.2.0",
"react": "19.1.0",
"react-native": "0.81.5",
"react-native-android-widget": "0.22.1",
-82
View File
@@ -1,82 +0,0 @@
diff --git a/node_modules/postal-mime/src/base64-decoder.js b/node_modules/postal-mime/src/base64-decoder.js
index 93c2aea..ba6e3f1 100644
--- a/node_modules/postal-mime/src/base64-decoder.js
+++ b/node_modules/postal-mime/src/base64-decoder.js
@@ -1,4 +1,4 @@
-import { decodeBase64, blobToArrayBuffer } from './decode-strings.js';
+import { decodeBase64, concatChunks } from './decode-strings.js';
export default class Base64Decoder {
constructor(opts) {
@@ -49,6 +49,6 @@ export default class Base64Decoder {
finalize() {
this.flushRemainder();
- return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return concatChunks(this.chunks);
}
}
diff --git a/node_modules/postal-mime/src/decode-strings.js b/node_modules/postal-mime/src/decode-strings.js
index a5e058e..f255e8d 100644
--- a/node_modules/postal-mime/src/decode-strings.js
+++ b/node_modules/postal-mime/src/decode-strings.js
@@ -145,6 +145,23 @@ export function getDecoder(charset) {
* @param {Blob} blob Blob to convert
* @returns {ArrayBuffer} Converted value
*/
+// React Native's Blob cannot be built from ArrayBuffers or typed arrays, so
+// the decoders join their chunks here instead of through a Blob.
+export function concatChunks(chunks) {
+ const parts = chunks.map(chunk => {
+ if (typeof chunk === 'string') return textEncoder.encode(chunk);
+ if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
+ });
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.byteLength, 0));
+ let pos = 0;
+ for (const part of parts) {
+ out.set(part, pos);
+ pos += part.byteLength;
+ }
+ return out.buffer;
+}
+
export async function blobToArrayBuffer(blob) {
if ('arrayBuffer' in blob) {
return await blob.arrayBuffer();
diff --git a/node_modules/postal-mime/src/pass-through-decoder.js b/node_modules/postal-mime/src/pass-through-decoder.js
index ad1c222..1e1d012 100644
--- a/node_modules/postal-mime/src/pass-through-decoder.js
+++ b/node_modules/postal-mime/src/pass-through-decoder.js
@@ -1,4 +1,4 @@
-import { blobToArrayBuffer } from './decode-strings.js';
+import { concatChunks } from './decode-strings.js';
export default class PassThroughDecoder {
constructor() {
@@ -12,6 +12,6 @@ export default class PassThroughDecoder {
finalize() {
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return concatChunks(this.chunks);
}
}
diff --git a/node_modules/postal-mime/src/qp-decoder.js b/node_modules/postal-mime/src/qp-decoder.js
index adee72c..3973673 100644
--- a/node_modules/postal-mime/src/qp-decoder.js
+++ b/node_modules/postal-mime/src/qp-decoder.js
@@ -1,4 +1,4 @@
-import { blobToArrayBuffer, hexNibble } from './decode-strings.js';
+import { concatChunks, hexNibble } from './decode-strings.js';
const CHR_EQUALS = 0x3d;
const CHR_LF = 0x0a;
@@ -83,6 +83,6 @@ export default class QPDecoder {
this.flushBuffer();
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return concatChunks(this.chunks);
}
}
+128
View File
@@ -0,0 +1,128 @@
diff --git a/node_modules/postal-mime/dist/cjs/chunk-writer.js b/node_modules/postal-mime/dist/cjs/chunk-writer.js
index aac95eb..3425832 100644
--- a/node_modules/postal-mime/dist/cjs/chunk-writer.js
+++ b/node_modules/postal-mime/dist/cjs/chunk-writer.js
@@ -57,7 +57,7 @@ class ChunkWriter {
finalize() {
this.flush();
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return (0, decode_strings_js_1.blobToArrayBuffer)(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return (0, decode_strings_js_1.concatChunks)(this.chunks);
}
}
exports.default = ChunkWriter;
diff --git a/node_modules/postal-mime/dist/cjs/decode-strings.js b/node_modules/postal-mime/dist/cjs/decode-strings.js
index 40c59f6..74b0c41 100644
--- a/node_modules/postal-mime/dist/cjs/decode-strings.js
+++ b/node_modules/postal-mime/dist/cjs/decode-strings.js
@@ -8,6 +8,7 @@ exports.decodeUtf8OrWindows1252 = decodeUtf8OrWindows1252;
exports.decodeCharset = decodeCharset;
exports.decodeText = decodeText;
exports.blobToArrayBuffer = blobToArrayBuffer;
+exports.concatChunks = concatChunks;
exports.hexNibble = hexNibble;
exports.foldLineBreaks = foldLineBreaks;
exports.decodeWord = decodeWord;
@@ -461,6 +462,22 @@ function decodeText(bytes, charset, html) {
}
return (decoder.encoding === 'x-user-defined' ? getDecoder('windows-1252') : decoder).decode(view);
}
+// React Native's Blob cannot be built from ArrayBuffers or typed arrays, so
+// the decoders join their chunks here instead of through a Blob.
+function concatChunks(chunks) {
+ const parts = chunks.map(chunk => {
+ if (typeof chunk === 'string') return exports.textEncoder.encode(chunk);
+ if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
+ });
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.byteLength, 0));
+ let pos = 0;
+ for (const part of parts) {
+ out.set(part, pos);
+ pos += part.byteLength;
+ }
+ return out.buffer;
+}
/**
* Converts a Blob into an ArrayBuffer
* @param blob Blob to convert
diff --git a/node_modules/postal-mime/dist/cjs/pass-through-decoder.js b/node_modules/postal-mime/dist/cjs/pass-through-decoder.js
index 755a1a7..d7f4ebc 100644
--- a/node_modules/postal-mime/dist/cjs/pass-through-decoder.js
+++ b/node_modules/postal-mime/dist/cjs/pass-through-decoder.js
@@ -68,7 +68,7 @@ class PassThroughDecoder {
}
this.flushRun();
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return (0, decode_strings_js_1.blobToArrayBuffer)(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return (0, decode_strings_js_1.concatChunks)(this.chunks);
}
}
exports.default = PassThroughDecoder;
diff --git a/node_modules/postal-mime/dist/esm/chunk-writer.js b/node_modules/postal-mime/dist/esm/chunk-writer.js
index eb22f8a..ceebace 100644
--- a/node_modules/postal-mime/dist/esm/chunk-writer.js
+++ b/node_modules/postal-mime/dist/esm/chunk-writer.js
@@ -1,4 +1,4 @@
-import { blobToArrayBuffer } from './decode-strings.js';
+import { concatChunks } from './decode-strings.js';
/**
* Collects decoded content in fixed size chunks, so memory follows the number of bytes
* written rather than the number of writes. Decoders that write many short runs, such as
@@ -55,7 +55,7 @@ export default class ChunkWriter {
finalize() {
this.flush();
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return concatChunks(this.chunks);
}
}
//# sourceMappingURL=chunk-writer.js.map
\ No newline at end of file
diff --git a/node_modules/postal-mime/dist/esm/decode-strings.js b/node_modules/postal-mime/dist/esm/decode-strings.js
index 7108f8d..3dbd459 100644
--- a/node_modules/postal-mime/dist/esm/decode-strings.js
+++ b/node_modules/postal-mime/dist/esm/decode-strings.js
@@ -443,6 +443,22 @@ export function decodeText(bytes, charset, html) {
}
return (decoder.encoding === 'x-user-defined' ? getDecoder('windows-1252') : decoder).decode(view);
}
+// React Native's Blob cannot be built from ArrayBuffers or typed arrays, so
+// the decoders join their chunks here instead of through a Blob.
+export function concatChunks(chunks) {
+ const parts = chunks.map(chunk => {
+ if (typeof chunk === 'string') return textEncoder.encode(chunk);
+ if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
+ return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
+ });
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.byteLength, 0));
+ let pos = 0;
+ for (const part of parts) {
+ out.set(part, pos);
+ pos += part.byteLength;
+ }
+ return out.buffer;
+}
/**
* Converts a Blob into an ArrayBuffer
* @param blob Blob to convert
diff --git a/node_modules/postal-mime/dist/esm/pass-through-decoder.js b/node_modules/postal-mime/dist/esm/pass-through-decoder.js
index 4d493af..39e6ad5 100644
--- a/node_modules/postal-mime/dist/esm/pass-through-decoder.js
+++ b/node_modules/postal-mime/dist/esm/pass-through-decoder.js
@@ -1,4 +1,4 @@
-import { blobToArrayBuffer } from './decode-strings.js';
+import { concatChunks } from './decode-strings.js';
// Hands the body through as it is, the line endings included.
//
// The line break is written when the next line arrives, not after each line. The CRLF in
@@ -66,7 +66,7 @@ export default class PassThroughDecoder {
}
this.flushRun();
// convert an array of arraybuffers into a blob and then back into a single arraybuffer
- return blobToArrayBuffer(new Blob(this.chunks, { type: 'application/octet-stream' }));
+ return concatChunks(this.chunks);
}
}
//# sourceMappingURL=pass-through-decoder.js.map
\ No newline at end of file
+1 -1
View File
@@ -4,7 +4,7 @@ import PostalMime from 'postal-mime';
// React Native's Blob only takes strings and other Blobs; building one from an
// ArrayBuffer or typed array throws. postal-mime used to join every decoded
// part through such a Blob, so an .eml with an attachment could not be
// previewed on the device (patches/postal-mime+3.0.0.patch).
// previewed on the device (patches/postal-mime+4.2.0.patch).
class ReactNativeBlob {
constructor(parts: unknown[] = []) {
if (parts.some((p) => p instanceof ArrayBuffer || ArrayBuffer.isView(p))) {
+1 -1
View File
@@ -150,7 +150,7 @@
},
{
"name": "postal-mime",
"version": "3.0.0",
"version": "4.2.0",
"license": "MIT-0",
"url": "https://postal-mime.postalsys.com"
},