Skip to content

Commit 2c775e6

Browse files
authored
feat: add stream-safe-creation.any.js WPT (nodejs#1701)
1 parent c7eb5f0 commit 2c775e6

File tree

4 files changed

+83
-3
lines changed

4 files changed

+83
-3
lines changed

lib/fetch/body.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const { File } = require('./file')
1616
const { StringDecoder } = require('string_decoder')
1717
const { parseMIMEType } = require('./dataURL')
1818

19+
/** @type {globalThis['ReadableStream']} */
1920
let ReadableStream
2021

2122
async function * blobGen (blob) {
@@ -196,11 +197,13 @@ function extractBody (object, keepalive = false) {
196197
},
197198
async cancel (reason) {
198199
await iterator.return()
199-
}
200+
},
201+
type: undefined
200202
})
201203
} else if (!stream) {
202204
// TODO: Spec doesn't say anything about this?
203205
stream = new ReadableStream({
206+
start () {},
204207
async pull (controller) {
205208
controller.enqueue(
206209
typeof source === 'string' ? new TextEncoder().encode(source) : source
@@ -218,7 +221,8 @@ function extractBody (object, keepalive = false) {
218221
}
219222
}
220223
})
221-
}
224+
},
225+
type: undefined
222226
})
223227
}
224228

lib/fetch/index.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const { TransformStream } = require('stream/web')
5757

5858
/** @type {import('buffer').resolveObjectURL} */
5959
let resolveObjectURL
60+
/** @type {globalThis['ReadableStream']} */
6061
let ReadableStream
6162

6263
const nodeVersion = process.versions.node.split('.')
@@ -930,6 +931,14 @@ async function fetchFinale (fetchParams, response) {
930931
start () {},
931932
transform: identityTransformAlgorithm,
932933
flush: processResponseEndOfBody
934+
}, {
935+
size () {
936+
return 1
937+
}
938+
}, {
939+
size () {
940+
return 1
941+
}
933942
})
934943

935944
// 4. Set response’s body to the result of piping response’s body through transformStream.
@@ -1758,7 +1767,12 @@ async function httpNetworkFetch (
17581767
await cancelAlgorithm(reason)
17591768
}
17601769
},
1761-
{ highWaterMark: 0 }
1770+
{
1771+
highWaterMark: 0,
1772+
size () {
1773+
return 1
1774+
}
1775+
}
17621776
)
17631777

17641778
// 17. Run these steps, but abort when the ongoing fetch is terminated:

test/wpt/status/fetch.status.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,13 @@
4040
"fail": [
4141
"Fetch with POST with text body on 421 response should be retried once on new connection."
4242
]
43+
},
44+
"stream-safe-creation.any.js": {
45+
"fail": [
46+
"throwing Object.prototype.type accessor should not affect stream creation by 'fetch'",
47+
"Object.prototype.type accessor returning invalid value should not affect stream creation by 'fetch'",
48+
"throwing Object.prototype.highWaterMark accessor should not affect stream creation by 'fetch'",
49+
"Object.prototype.highWaterMark accessor returning invalid value should not affect stream creation by 'fetch'"
50+
]
4351
}
4452
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// META: global=window,worker
2+
3+
// These tests verify that stream creation is not affected by changes to
4+
// Object.prototype.
5+
6+
const creationCases = {
7+
fetch: async () => fetch(location.href),
8+
request: () => new Request(location.href, {method: 'POST', body: 'hi'}),
9+
response: () => new Response('bye'),
10+
consumeEmptyResponse: () => new Response().text(),
11+
consumeNonEmptyResponse: () => new Response(new Uint8Array([64])).text(),
12+
consumeEmptyRequest: () => new Request(location.href).text(),
13+
consumeNonEmptyRequest: () => new Request(location.href,
14+
{method: 'POST', body: 'yes'}).arrayBuffer(),
15+
};
16+
17+
for (const creationCase of Object.keys(creationCases)) {
18+
for (const accessorName of ['start', 'type', 'size', 'highWaterMark']) {
19+
promise_test(async t => {
20+
Object.defineProperty(Object.prototype, accessorName, {
21+
get() { throw Error(`Object.prototype.${accessorName} was accessed`); },
22+
configurable: true
23+
});
24+
t.add_cleanup(() => {
25+
delete Object.prototype[accessorName];
26+
return Promise.resolve();
27+
});
28+
await creationCases[creationCase]();
29+
}, `throwing Object.prototype.${accessorName} accessor should not affect ` +
30+
`stream creation by '${creationCase}'`);
31+
32+
promise_test(async t => {
33+
// -1 is a convenient value which is invalid, and should cause the
34+
// constructor to throw, for all four fields.
35+
Object.prototype[accessorName] = -1;
36+
t.add_cleanup(() => {
37+
delete Object.prototype[accessorName];
38+
return Promise.resolve();
39+
});
40+
await creationCases[creationCase]();
41+
}, `Object.prototype.${accessorName} accessor returning invalid value ` +
42+
`should not affect stream creation by '${creationCase}'`);
43+
}
44+
45+
promise_test(async t => {
46+
Object.prototype.start = controller => controller.error(new Error('start'));
47+
t.add_cleanup(() => {
48+
delete Object.prototype.start;
49+
return Promise.resolve();
50+
});
51+
await creationCases[creationCase]();
52+
}, `Object.prototype.start function which errors the stream should not ` +
53+
`affect stream creation by '${creationCase}'`);
54+
}

0 commit comments

Comments
 (0)