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

fetch: implement new spec changes #1721

Merged
merged 4 commits into from Oct 23, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
65 changes: 37 additions & 28 deletions lib/fetch/body.js
Expand Up @@ -19,33 +19,45 @@ const { parseMIMEType, serializeAMimeType } = require('./dataURL')
/** @type {globalThis['ReadableStream']} */
let ReadableStream

async function * blobGen (blob) {
yield * blob.stream()
}

// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
function extractBody (object, keepalive = false) {
if (!ReadableStream) {
ReadableStream = require('stream/web').ReadableStream
}

// 1. Let stream be object if object is a ReadableStream object.
// Otherwise, let stream be a new ReadableStream, and set up stream.
// 1. Let stream be null.
let stream = null

// 2. Let action be null.
// 2. If object is a ReadableStream object, then set stream to object.
if (object instanceof ReadableStream) {
stream = object
} else if (isBlobLike(object)) {
// 3. Otherwise, if object is a Blob object, set stream to the
// result of running object’s get stream.
stream = object.stream()
}

// 4. Otherwise, set stream to a new ReadableStream object, and set
// up stream.

// 5. Assert: stream is a ReadableStream object.
KhafraDev marked this conversation as resolved.
Show resolved Hide resolved

// TODO(@KhafraDev): setting stream to an empty stream here causes
// the process to unexpectedly exit. An error is likely being swallowed.

// 6. Let action be null.
let action = null

// 3. Let source be null.
// 7. Let source be null.
let source = null

// 4. Let length be null.
// 8. Let length be null.
let length = null

// 5. Let Content-Type be null.
let contentType = null
// 9. Let type be null.
let type = null

// 6. Switch on object:
// 10. Switch on object:
if (object == null) {
// Note: The IDL processor cannot handle this situation. See
// https://crbug.com/335871.
Expand All @@ -60,8 +72,8 @@ function extractBody (object, keepalive = false) {
// Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list.
source = object.toString()

// Set Content-Type to `application/x-www-form-urlencoded;charset=UTF-8`.
contentType = 'application/x-www-form-urlencoded;charset=UTF-8'
// Set type to `application/x-www-form-urlencoded;charset=UTF-8`.
type = 'application/x-www-form-urlencoded;charset=UTF-8'
} else if (isArrayBuffer(object)) {
// BufferSource/ArrayBuffer

Expand Down Expand Up @@ -104,7 +116,7 @@ function extractBody (object, keepalive = false) {
}\r\n\r\n`
)

yield * blobGen(value)
yield * value.stream()

yield enc.encode('\r\n')
}
Expand All @@ -119,26 +131,23 @@ function extractBody (object, keepalive = false) {
// Set length to unclear, see html/6424 for improving this.
// TODO

// Set Content-Type to `multipart/form-data; boundary=`,
// Set type to `multipart/form-data; boundary=`,
// followed by the multipart/form-data boundary string generated
// by the multipart/form-data encoding algorithm.
contentType = 'multipart/form-data; boundary=' + boundary
type = 'multipart/form-data; boundary=' + boundary
} else if (isBlobLike(object)) {
// Blob

// Set action to this step: read object.
action = blobGen

// Set source to object.
source = object

// Set length to object’s size.
length = object.size

// If object’s type attribute is not the empty byte sequence, set
// Content-Type to its value.
// type to its value.
if (object.type) {
contentType = object.type
type = object.type
}
} else if (typeof object[Symbol.asyncIterator] === 'function') {
// If keepalive is true, then throw a TypeError.
Expand All @@ -160,17 +169,17 @@ function extractBody (object, keepalive = false) {
// TODO: scalar value string?
// TODO: else?
source = toUSVString(object)
contentType = 'text/plain;charset=UTF-8'
type = 'text/plain;charset=UTF-8'
}

// 7. If source is a byte sequence, then set action to a
// 11. If source is a byte sequence, then set action to a
// step that returns source and length to source’s length.
// TODO: What is a "byte sequence?"
if (typeof source === 'string' || util.isBuffer(source)) {
length = Buffer.byteLength(source)
}

// 8. If action is non-null, then run these steps in in parallel:
// 12. If action is non-null, then run these steps in in parallel:
if (action != null) {
// Run action.
let iterator
Expand Down Expand Up @@ -226,12 +235,12 @@ function extractBody (object, keepalive = false) {
})
}

// 9. Let body be a body whose stream is stream, source is source,
// 13. Let body be a body whose stream is stream, source is source,
// and length is length.
const body = { stream, source, length }

// 10. Return body and Content-Type.
return [body, contentType]
// 14. Return (body, type).
return [body, type]
}

// https://fetch.spec.whatwg.org/#bodyinit-safely-extract
Expand Down
48 changes: 28 additions & 20 deletions lib/fetch/index.js
Expand Up @@ -39,7 +39,7 @@ const {
} = require('./util')
const { kState, kHeaders, kGuard, kRealm } = require('./symbols')
const assert = require('assert')
const { safelyExtractBody, extractBody } = require('./body')
const { safelyExtractBody } = require('./body')
const {
redirectStatus,
nullBodyStatus,
Expand Down Expand Up @@ -427,8 +427,8 @@ function fetching ({
crossOriginIsolatedCapability
}

// 7. If request’s body is a byte sequence, then set request’s body to the
// first return value of safely extracting request’s body.
// 7. If request’s body is a byte sequence, then set request’s body to
// request’s body as a body.
// NOTE: Since fetching is only called from fetch, body should already be
// extracted.
assert(!request.body || request.body.stream)
Expand Down Expand Up @@ -758,8 +758,7 @@ async function mainFetch (fetchParams, recursive = false) {
return
}

// 2. Set response’s body to the first return value of safely
// extracting bytes.
// 2. Set response’s body to bytes as a body.
response.body = safelyExtractBody(bytes)[0]

// 3. Run fetch finale given fetchParams and response.
Expand Down Expand Up @@ -787,7 +786,7 @@ async function schemeFetch (fetchParams) {
case 'about:': {
// If request’s current URL’s path is the string "blank", then return a new response
// whose status message is `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) »,
// and body is the empty byte sequence.
// and body is the empty byte sequence as a body.

// Otherwise, return a network error.
return makeNetworkError('about scheme is not supported')
Expand All @@ -809,27 +808,36 @@ async function schemeFetch (fetchParams) {

const blob = resolveObjectURL(currentURL.toString())

// 2. If request’s method is not `GET` or blob is not a Blob object, then return a network error. [FILEAPI]
// 2. If request’s method is not `GET` or blob is not a Blob object, then return a network error.
if (request.method !== 'GET' || !isBlobLike(blob)) {
return makeNetworkError('invalid method')
}

// 3. Let response be a new response whose status message is `OK`.
const response = makeResponse({ statusText: 'OK', urlList: [currentURL] })
// 3. Let bodyWithType be the result of safely extracting blobURLEntry’s object.
const bodyWithType = safelyExtractBody(blob)

// 4. Append (`Content-Length`, blob’s size attribute value) to response’s header list.
response.headersList.set('content-length', `${blob.size}`)
// 4. Let body be bodyWithType’s body.
const body = bodyWithType[0]

// 5. Append (`Content-Type`, blob’s type attribute value) to response’s header list.
response.headersList.set('content-type', blob.type)
// 5. Let length be body’s length, serialized and isomorphic encoded.
const length = `${body.length}`

// 6. Set response’s body to the result of performing the read operation on blob.
// TODO (fix): This needs to read?
response.body = extractBody(blob)[0]
// 6. Let type be bodyWithType’s type if it is non-null; otherwise the empty byte sequence.
const type = bodyWithType[1] ?? ''

// 7. Return response.
return response
// 7. Return a new response whose status message is `OK`, header list is
// « (`Content-Length`, length), (`Content-Type`, type) », and body is body.
const response = makeResponse({
statusText: 'OK',
headersList: [
['content-length', length],
['content-type', type]
]
})

response.body = body

return response
// 2. If aborted, then return the appropriate network error for fetchParams.
// TODO
}
Expand All @@ -850,13 +858,13 @@ async function schemeFetch (fetchParams) {

// 4. Return a response whose status message is `OK`,
// header list is « (`Content-Type`, mimeType) »,
// and body is dataURLStruct’s body.
// and body is dataURLStruct’s body as a body.
return makeResponse({
statusText: 'OK',
headersList: [
['content-type', mimeType]
],
body: extractBody(dataURLStruct.body)[0]
body: safelyExtractBody(dataURLStruct.body)[0]
KhafraDev marked this conversation as resolved.
Show resolved Hide resolved
})
}
case 'file:': {
Expand Down