Skip to content

Commit 5b3e78a

Browse files
authored
Merge branch 'master' into fix/octetstream-size-limits
2 parents f714ffd + b1dcb08 commit 5b3e78a

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

src/PersistentFile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class PersistentFile extends EventEmitter {
6565
this.hash.update(buffer);
6666
}
6767

68-
if (this._writeStream.closed) {
68+
if (this._writeStream.closed || this._writeStream.destroyed) {
6969
cb();
7070
return;
7171
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* eslint-disable no-underscore-dangle */
2+
import { jest } from "@jest/globals";
3+
import PersistentFile from "../../src/PersistentFile.js";
4+
5+
// Covers https://github.com/node-formidable/formidable/issues/958: writing
6+
// to a PersistentFile after its underlying write stream was destroyed (but
7+
// before the stream's "close" event has fired, i.e. `.closed` is still
8+
// false) must not attempt to write to it, since Node throws
9+
// ERR_STREAM_DESTROYED ("Cannot call write after a stream was destroyed")
10+
// for that, and that error surfaces outside of any catchable callback.
11+
describe("PersistentFile write() guards against a destroyed stream", () => {
12+
let file;
13+
let writeStreamMock;
14+
15+
beforeEach(() => {
16+
file = new PersistentFile({
17+
filepath: "/tmp/cat.png",
18+
originalFilename: "cat.png",
19+
newFilename: "dff1d2eaab9752165764dcd00",
20+
mimetype: "image/png",
21+
});
22+
23+
writeStreamMock = {
24+
closed: false,
25+
destroyed: false,
26+
// Always resolves, whether or not the guard under test should have
27+
// short-circuited before reaching here - that way an unguarded write
28+
// still completes `file.write()`'s callback promptly, so a broken
29+
// guard fails its assertion immediately instead of timing out.
30+
write: jest.fn((writeBuffer, cb) => cb()),
31+
};
32+
file._writeStream = writeStreamMock;
33+
});
34+
35+
test("write() calls through to the stream when it is neither closed nor destroyed", (done) => {
36+
const buffer = Buffer.alloc(5);
37+
38+
file.write(buffer, () => {
39+
expect(writeStreamMock.write).toBeCalledWith(
40+
buffer,
41+
expect.any(Function)
42+
);
43+
done();
44+
});
45+
});
46+
47+
test("write() is a no-op once the stream is closed", (done) => {
48+
writeStreamMock.closed = true;
49+
50+
file.write(Buffer.alloc(5), () => {
51+
expect(writeStreamMock.write).not.toBeCalled();
52+
done();
53+
});
54+
});
55+
56+
test("write() is a no-op once the stream is destroyed, even while closed is still false", (done) => {
57+
// This is the state a request-aborted destroy() can leave the stream in:
58+
// `destroyed` flips synchronously, `closed` only follows once the
59+
// underlying fd finishes closing.
60+
writeStreamMock.destroyed = true;
61+
62+
file.write(Buffer.alloc(5), () => {
63+
expect(writeStreamMock.write).not.toBeCalled();
64+
done();
65+
});
66+
});
67+
});

0 commit comments

Comments
 (0)