Skip to content

Commit

Permalink
implement spec changes from a while ago (nodejs#2676)
Browse files Browse the repository at this point in the history
* implement spec changes from a while ago

* fixup
  • Loading branch information
KhafraDev authored and crysmags committed Feb 27, 2024
1 parent 4bedb9d commit ac05e00
Show file tree
Hide file tree
Showing 4 changed files with 277 additions and 68 deletions.
42 changes: 25 additions & 17 deletions lib/fetch/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,19 @@ const {
isReadableStreamLike,
readableStreamClose,
createDeferredPromise,
fullyReadBody
fullyReadBody,
extractMimeType
} = require('./util')
const { FormData } = require('./formdata')
const { kState } = require('./symbols')
const { webidl } = require('./webidl')
const { Blob, File: NativeFile } = require('node:buffer')
const { kBodyUsed, kHeadersList } = require('../core/symbols')
const { kBodyUsed } = require('../core/symbols')
const assert = require('node:assert')
const { isErrored } = require('../core/util')
const { isUint8Array, isArrayBuffer } = require('util/types')
const { File: UndiciFile } = require('./file')
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
const { serializeAMimeType } = require('./dataURL')

/** @type {globalThis['File']} */
const File = NativeFile ?? UndiciFile
Expand Down Expand Up @@ -330,7 +331,7 @@ function bodyMixinMethods (instance) {
return specConsumeBody(this, (bytes) => {
let mimeType = bodyMimeType(this)

if (mimeType === 'failure') {
if (mimeType === null) {
mimeType = ''
} else if (mimeType) {
mimeType = serializeAMimeType(mimeType)
Expand Down Expand Up @@ -369,12 +370,11 @@ function bodyMixinMethods (instance) {

throwIfAborted(this[kState])

const contentType = this.headers[kHeadersList].get('content-type', true)

const mimeType = contentType !== null ? parseMIMEType(contentType) : 'failure'
// 1. Let mimeType be the result of get the MIME type with this.
const mimeType = bodyMimeType(this)

// If mimeType’s essence is "multipart/form-data", then:
if (mimeType !== 'failure' && mimeType.essence === 'multipart/form-data') {
if (mimeType !== null && mimeType.essence === 'multipart/form-data') {
const headers = {}
for (const [key, value] of this.headers) headers[key] = value

Expand Down Expand Up @@ -432,7 +432,7 @@ function bodyMixinMethods (instance) {
await busboyResolve

return responseFormData
} else if (mimeType !== 'failure' && mimeType.essence === 'application/x-www-form-urlencoded') {
} else if (mimeType !== null && mimeType.essence === 'application/x-www-form-urlencoded') {
// Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then:

// 1. Let entries be the result of parsing bytes.
Expand Down Expand Up @@ -581,17 +581,25 @@ function parseJSONFromBytes (bytes) {

/**
* @see https://fetch.spec.whatwg.org/#concept-body-mime-type
* @param {import('./response').Response|import('./request').Request} object
* @param {import('./response').Response|import('./request').Request} requestOrResponse
*/
function bodyMimeType (object) {
const { headersList } = object[kState]
const contentType = headersList.get('content-type')

if (contentType === null) {
return 'failure'
function bodyMimeType (requestOrResponse) {
// 1. Let headers be null.
// 2. If requestOrResponse is a Request object, then set headers to requestOrResponse’s request’s header list.
// 3. Otherwise, set headers to requestOrResponse’s response’s header list.
/** @type {import('./headers').HeadersList} */
const headers = requestOrResponse[kState].headersList

// 4. Let mimeType be the result of extracting a MIME type from headers.
const mimeType = extractMimeType(headers)

// 5. If mimeType is failure, then return null.
if (mimeType === 'failure') {
return null
}

return parseMIMEType(contentType)
// 6. Return mimeType.
return mimeType
}

module.exports = {
Expand Down
112 changes: 96 additions & 16 deletions lib/fetch/dataURL.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const assert = require('node:assert')
const { isomorphicDecode } = require('./util')

const encoder = new TextEncoder()

Expand Down Expand Up @@ -604,18 +603,7 @@ function isHTTPWhiteSpace (char) {
* @param {boolean} [trailing=true]
*/
function removeHTTPWhitespace (str, leading = true, trailing = true) {
let lead = 0
let trail = str.length - 1

if (leading) {
while (lead < str.length && isHTTPWhiteSpace(str.charCodeAt(lead))) lead++
}

if (trailing) {
while (trail > 0 && isHTTPWhiteSpace(str.charCodeAt(trail))) trail--
}

return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
return removeChars(str, leading, trailing, isHTTPWhiteSpace)
}

/**
Expand All @@ -634,20 +622,110 @@ function isASCIIWhitespace (char) {
* @param {boolean} [trailing=true]
*/
function removeASCIIWhitespace (str, leading = true, trailing = true) {
return removeChars(str, leading, trailing, isASCIIWhitespace)
}

/**
*
* @param {string} str
* @param {boolean} leading
* @param {boolean} trailing
* @param {(charCode: number) => boolean} predicate
* @returns
*/
function removeChars (str, leading, trailing, predicate) {
let lead = 0
let trail = str.length - 1

if (leading) {
while (lead < str.length && isASCIIWhitespace(str.charCodeAt(lead))) lead++
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
}

if (trailing) {
while (trail > 0 && isASCIIWhitespace(str.charCodeAt(trail))) trail--
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
}

return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
}

/**
* @see https://infra.spec.whatwg.org/#isomorphic-decode
* @param {Uint8Array} input
* @returns {string}
*/
function isomorphicDecode (input) {
// 1. To isomorphic decode a byte sequence input, return a string whose code point
// length is equal to input’s length and whose code points have the same values
// as the values of input’s bytes, in the same order.
const length = input.length
if ((2 << 15) - 1 > length) {
return String.fromCharCode.apply(null, input)
}
let result = ''; let i = 0
let addition = (2 << 15) - 1
while (i < length) {
if (i + addition > length) {
addition = length - i
}
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
}
return result
}

/**
* @see https://mimesniff.spec.whatwg.org/#minimize-a-supported-mime-type
* @param {Exclude<ReturnType<typeof parseMIMEType>, 'failure'>} mimeType
*/
function minimizeSupportedMimeType (mimeType) {
switch (mimeType.essence) {
case 'application/ecmascript':
case 'application/javascript':
case 'application/x-ecmascript':
case 'application/x-javascript':
case 'text/ecmascript':
case 'text/javascript':
case 'text/javascript1.0':
case 'text/javascript1.1':
case 'text/javascript1.2':
case 'text/javascript1.3':
case 'text/javascript1.4':
case 'text/javascript1.5':
case 'text/jscript':
case 'text/livescript':
case 'text/x-ecmascript':
case 'text/x-javascript':
// 1. If mimeType is a JavaScript MIME type, then return "text/javascript".
return 'text/javascript'
case 'application/json':
case 'text/json':
// 2. If mimeType is a JSON MIME type, then return "application/json".
return 'application/json'
case 'image/svg+xml':
// 3. If mimeType’s essence is "image/svg+xml", then return "image/svg+xml".
return 'image/svg+xml'
case 'text/xml':
case 'application/xml':
// 4. If mimeType is an XML MIME type, then return "application/xml".
return 'application/xml'
}

// 2. If mimeType is a JSON MIME type, then return "application/json".
if (mimeType.subtype.endsWith('+json')) {
return 'application/json'
}

// 4. If mimeType is an XML MIME type, then return "application/xml".
if (mimeType.subtype.endsWith('+xml')) {
return 'application/xml'
}

// 5. If mimeType is supported by the user agent, then return mimeType’s essence.
// Technically, node doesn't support any mimetypes.

// 6. Return the empty string.
return ''
}

module.exports = {
dataURLProcessor,
URLSerializer,
Expand All @@ -656,5 +734,7 @@ module.exports = {
stringPercentDecode,
parseMIMEType,
collectAnHTTPQuotedString,
serializeAMimeType
serializeAMimeType,
removeChars,
minimizeSupportedMimeType
}
9 changes: 5 additions & 4 deletions lib/fetch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ const {
clampAndCoarsenConnectionTimingInfo,
simpleRangeHeaderValue,
buildContentRange,
createInflate
createInflate,
extractMimeType
} = require('./util')
const { kState } = require('./symbols')
const assert = require('node:assert')
Expand All @@ -59,7 +60,7 @@ const {
const EE = require('node:events')
const { Readable, pipeline } = require('node:stream')
const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor, bufferToLowerCasedHeaderName } = require('../core/util')
const { dataURLProcessor, serializeAMimeType, parseMIMEType } = require('./dataURL')
const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./dataURL')
const { getGlobalDispatcher } = require('../global')
const { webidl } = require('./webidl')
const { STATUS_CODES } = require('node:http')
Expand Down Expand Up @@ -1027,11 +1028,11 @@ function fetchFinale (fetchParams, response) {
responseStatus = response.status

// 2. Let mimeType be the result of extracting a MIME type from response’s header list.
const mimeType = parseMIMEType(response.headersList.get('content-type', true)) // TODO: fix
const mimeType = extractMimeType(response.headersList)

// 3. If mimeType is not failure, then set bodyInfo’s content type to the result of minimizing a supported MIME type given mimeType.
if (mimeType !== 'failure') {
// TODO
bodyInfo.contentType = minimizeSupportedMimeType(mimeType)
}
}

Expand Down
Loading

0 comments on commit ac05e00

Please sign in to comment.