Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add stream-safe-creation.any.js WPT #1701

Merged
merged 2 commits into from Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/fetch/body.js
Expand Up @@ -16,6 +16,7 @@ const { File } = require('./file')
const { StringDecoder } = require('string_decoder')
const { parseMIMEType } = require('./dataURL')

/** @type {globalThis['ReadableStream']} */
let ReadableStream

async function * blobGen (blob) {
Expand Down Expand Up @@ -196,11 +197,13 @@ function extractBody (object, keepalive = false) {
},
async cancel (reason) {
await iterator.return()
}
},
type: undefined
})
} else if (!stream) {
// TODO: Spec doesn't say anything about this?
stream = new ReadableStream({
start () {},
async pull (controller) {
controller.enqueue(
typeof source === 'string' ? new TextEncoder().encode(source) : source
Expand All @@ -218,7 +221,8 @@ function extractBody (object, keepalive = false) {
}
}
})
}
},
type: undefined
})
}

Expand Down
16 changes: 15 additions & 1 deletion lib/fetch/index.js
Expand Up @@ -57,6 +57,7 @@ const { TransformStream } = require('stream/web')

/** @type {import('buffer').resolveObjectURL} */
let resolveObjectURL
/** @type {globalThis['ReadableStream']} */
let ReadableStream

const nodeVersion = process.versions.node.split('.')
Expand Down Expand Up @@ -930,6 +931,14 @@ async function fetchFinale (fetchParams, response) {
start () {},
transform: identityTransformAlgorithm,
flush: processResponseEndOfBody
}, {
size () {
return 1
}
}, {
size () {
return 1
}
})

// 4. Set response’s body to the result of piping response’s body through transformStream.
Expand Down Expand Up @@ -1758,7 +1767,12 @@ async function httpNetworkFetch (
await cancelAlgorithm(reason)
}
},
{ highWaterMark: 0 }
{
highWaterMark: 0,
size () {
return 1
}
}
)

// 17. Run these steps, but abort when the ongoing fetch is terminated:
Expand Down
8 changes: 8 additions & 0 deletions test/wpt/status/fetch.status.json
Expand Up @@ -40,5 +40,13 @@
"fail": [
"Fetch with POST with text body on 421 response should be retried once on new connection."
]
},
"stream-safe-creation.any.js": {
"fail": [
"throwing Object.prototype.type accessor should not affect stream creation by 'fetch'",
"Object.prototype.type accessor returning invalid value should not affect stream creation by 'fetch'",
"throwing Object.prototype.highWaterMark accessor should not affect stream creation by 'fetch'",
"Object.prototype.highWaterMark accessor returning invalid value should not affect stream creation by 'fetch'"
]
}
}
54 changes: 54 additions & 0 deletions test/wpt/tests/fetch/api/basic/stream-safe-creation.any.js
@@ -0,0 +1,54 @@
// META: global=window,worker

// These tests verify that stream creation is not affected by changes to
// Object.prototype.

const creationCases = {
fetch: async () => fetch(location.href),
request: () => new Request(location.href, {method: 'POST', body: 'hi'}),
response: () => new Response('bye'),
consumeEmptyResponse: () => new Response().text(),
consumeNonEmptyResponse: () => new Response(new Uint8Array([64])).text(),
consumeEmptyRequest: () => new Request(location.href).text(),
consumeNonEmptyRequest: () => new Request(location.href,
{method: 'POST', body: 'yes'}).arrayBuffer(),
};

for (const creationCase of Object.keys(creationCases)) {
for (const accessorName of ['start', 'type', 'size', 'highWaterMark']) {
promise_test(async t => {
Object.defineProperty(Object.prototype, accessorName, {
get() { throw Error(`Object.prototype.${accessorName} was accessed`); },
configurable: true
});
t.add_cleanup(() => {
delete Object.prototype[accessorName];
return Promise.resolve();
});
await creationCases[creationCase]();
}, `throwing Object.prototype.${accessorName} accessor should not affect ` +
`stream creation by '${creationCase}'`);

promise_test(async t => {
// -1 is a convenient value which is invalid, and should cause the
// constructor to throw, for all four fields.
Object.prototype[accessorName] = -1;
t.add_cleanup(() => {
delete Object.prototype[accessorName];
return Promise.resolve();
});
await creationCases[creationCase]();
}, `Object.prototype.${accessorName} accessor returning invalid value ` +
`should not affect stream creation by '${creationCase}'`);
}

promise_test(async t => {
Object.prototype.start = controller => controller.error(new Error('start'));
t.add_cleanup(() => {
delete Object.prototype.start;
return Promise.resolve();
});
await creationCases[creationCase]();
}, `Object.prototype.start function which errors the stream should not ` +
`affect stream creation by '${creationCase}'`);
}