Skip to content

Commit 8220e7d

Browse files
authored
implement spec changes from a while ago (nodejs#2676)
* implement spec changes from a while ago * fixup
1 parent f1d7ada commit 8220e7d

File tree

4 files changed

+277
-68
lines changed

4 files changed

+277
-68
lines changed

lib/fetch/body.js

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@ const {
88
isReadableStreamLike,
99
readableStreamClose,
1010
createDeferredPromise,
11-
fullyReadBody
11+
fullyReadBody,
12+
extractMimeType
1213
} = require('./util')
1314
const { FormData } = require('./formdata')
1415
const { kState } = require('./symbols')
1516
const { webidl } = require('./webidl')
1617
const { Blob, File: NativeFile } = require('node:buffer')
17-
const { kBodyUsed, kHeadersList } = require('../core/symbols')
18+
const { kBodyUsed } = require('../core/symbols')
1819
const assert = require('node:assert')
1920
const { isErrored } = require('../core/util')
2021
const { isUint8Array, isArrayBuffer } = require('util/types')
2122
const { File: UndiciFile } = require('./file')
22-
const { parseMIMEType, serializeAMimeType } = require('./dataURL')
23+
const { serializeAMimeType } = require('./dataURL')
2324

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

333-
if (mimeType === 'failure') {
334+
if (mimeType === null) {
334335
mimeType = ''
335336
} else if (mimeType) {
336337
mimeType = serializeAMimeType(mimeType)
@@ -369,12 +370,11 @@ function bodyMixinMethods (instance) {
369370

370371
throwIfAborted(this[kState])
371372

372-
const contentType = this.headers[kHeadersList].get('content-type', true)
373-
374-
const mimeType = contentType !== null ? parseMIMEType(contentType) : 'failure'
373+
// 1. Let mimeType be the result of get the MIME type with this.
374+
const mimeType = bodyMimeType(this)
375375

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

@@ -432,7 +432,7 @@ function bodyMixinMethods (instance) {
432432
await busboyResolve
433433

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

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

582582
/**
583583
* @see https://fetch.spec.whatwg.org/#concept-body-mime-type
584-
* @param {import('./response').Response|import('./request').Request} object
584+
* @param {import('./response').Response|import('./request').Request} requestOrResponse
585585
*/
586-
function bodyMimeType (object) {
587-
const { headersList } = object[kState]
588-
const contentType = headersList.get('content-type')
589-
590-
if (contentType === null) {
591-
return 'failure'
586+
function bodyMimeType (requestOrResponse) {
587+
// 1. Let headers be null.
588+
// 2. If requestOrResponse is a Request object, then set headers to requestOrResponse’s request’s header list.
589+
// 3. Otherwise, set headers to requestOrResponse’s response’s header list.
590+
/** @type {import('./headers').HeadersList} */
591+
const headers = requestOrResponse[kState].headersList
592+
593+
// 4. Let mimeType be the result of extracting a MIME type from headers.
594+
const mimeType = extractMimeType(headers)
595+
596+
// 5. If mimeType is failure, then return null.
597+
if (mimeType === 'failure') {
598+
return null
592599
}
593600

594-
return parseMIMEType(contentType)
601+
// 6. Return mimeType.
602+
return mimeType
595603
}
596604

597605
module.exports = {

lib/fetch/dataURL.js

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const assert = require('node:assert')
2-
const { isomorphicDecode } = require('./util')
32

43
const encoder = new TextEncoder()
54

@@ -604,18 +603,7 @@ function isHTTPWhiteSpace (char) {
604603
* @param {boolean} [trailing=true]
605604
*/
606605
function removeHTTPWhitespace (str, leading = true, trailing = true) {
607-
let lead = 0
608-
let trail = str.length - 1
609-
610-
if (leading) {
611-
while (lead < str.length && isHTTPWhiteSpace(str.charCodeAt(lead))) lead++
612-
}
613-
614-
if (trailing) {
615-
while (trail > 0 && isHTTPWhiteSpace(str.charCodeAt(trail))) trail--
616-
}
617-
618-
return lead === 0 && trail === str.length - 1 ? str : str.slice(lead, trail + 1)
606+
return removeChars(str, leading, trailing, isHTTPWhiteSpace)
619607
}
620608

621609
/**
@@ -634,20 +622,110 @@ function isASCIIWhitespace (char) {
634622
* @param {boolean} [trailing=true]
635623
*/
636624
function removeASCIIWhitespace (str, leading = true, trailing = true) {
625+
return removeChars(str, leading, trailing, isASCIIWhitespace)
626+
}
627+
628+
/**
629+
*
630+
* @param {string} str
631+
* @param {boolean} leading
632+
* @param {boolean} trailing
633+
* @param {(charCode: number) => boolean} predicate
634+
* @returns
635+
*/
636+
function removeChars (str, leading, trailing, predicate) {
637637
let lead = 0
638638
let trail = str.length - 1
639639

640640
if (leading) {
641-
while (lead < str.length && isASCIIWhitespace(str.charCodeAt(lead))) lead++
641+
while (lead < str.length && predicate(str.charCodeAt(lead))) lead++
642642
}
643643

644644
if (trailing) {
645-
while (trail > 0 && isASCIIWhitespace(str.charCodeAt(trail))) trail--
645+
while (trail > 0 && predicate(str.charCodeAt(trail))) trail--
646646
}
647647

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

651+
/**
652+
* @see https://infra.spec.whatwg.org/#isomorphic-decode
653+
* @param {Uint8Array} input
654+
* @returns {string}
655+
*/
656+
function isomorphicDecode (input) {
657+
// 1. To isomorphic decode a byte sequence input, return a string whose code point
658+
// length is equal to input’s length and whose code points have the same values
659+
// as the values of input’s bytes, in the same order.
660+
const length = input.length
661+
if ((2 << 15) - 1 > length) {
662+
return String.fromCharCode.apply(null, input)
663+
}
664+
let result = ''; let i = 0
665+
let addition = (2 << 15) - 1
666+
while (i < length) {
667+
if (i + addition > length) {
668+
addition = length - i
669+
}
670+
result += String.fromCharCode.apply(null, input.subarray(i, i += addition))
671+
}
672+
return result
673+
}
674+
675+
/**
676+
* @see https://mimesniff.spec.whatwg.org/#minimize-a-supported-mime-type
677+
* @param {Exclude<ReturnType<typeof parseMIMEType>, 'failure'>} mimeType
678+
*/
679+
function minimizeSupportedMimeType (mimeType) {
680+
switch (mimeType.essence) {
681+
case 'application/ecmascript':
682+
case 'application/javascript':
683+
case 'application/x-ecmascript':
684+
case 'application/x-javascript':
685+
case 'text/ecmascript':
686+
case 'text/javascript':
687+
case 'text/javascript1.0':
688+
case 'text/javascript1.1':
689+
case 'text/javascript1.2':
690+
case 'text/javascript1.3':
691+
case 'text/javascript1.4':
692+
case 'text/javascript1.5':
693+
case 'text/jscript':
694+
case 'text/livescript':
695+
case 'text/x-ecmascript':
696+
case 'text/x-javascript':
697+
// 1. If mimeType is a JavaScript MIME type, then return "text/javascript".
698+
return 'text/javascript'
699+
case 'application/json':
700+
case 'text/json':
701+
// 2. If mimeType is a JSON MIME type, then return "application/json".
702+
return 'application/json'
703+
case 'image/svg+xml':
704+
// 3. If mimeType’s essence is "image/svg+xml", then return "image/svg+xml".
705+
return 'image/svg+xml'
706+
case 'text/xml':
707+
case 'application/xml':
708+
// 4. If mimeType is an XML MIME type, then return "application/xml".
709+
return 'application/xml'
710+
}
711+
712+
// 2. If mimeType is a JSON MIME type, then return "application/json".
713+
if (mimeType.subtype.endsWith('+json')) {
714+
return 'application/json'
715+
}
716+
717+
// 4. If mimeType is an XML MIME type, then return "application/xml".
718+
if (mimeType.subtype.endsWith('+xml')) {
719+
return 'application/xml'
720+
}
721+
722+
// 5. If mimeType is supported by the user agent, then return mimeType’s essence.
723+
// Technically, node doesn't support any mimetypes.
724+
725+
// 6. Return the empty string.
726+
return ''
727+
}
728+
651729
module.exports = {
652730
dataURLProcessor,
653731
URLSerializer,
@@ -656,5 +734,7 @@ module.exports = {
656734
stringPercentDecode,
657735
parseMIMEType,
658736
collectAnHTTPQuotedString,
659-
serializeAMimeType
737+
serializeAMimeType,
738+
removeChars,
739+
minimizeSupportedMimeType
660740
}

lib/fetch/index.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ const {
4444
clampAndCoarsenConnectionTimingInfo,
4545
simpleRangeHeaderValue,
4646
buildContentRange,
47-
createInflate
47+
createInflate,
48+
extractMimeType
4849
} = require('./util')
4950
const { kState } = require('./symbols')
5051
const assert = require('node:assert')
@@ -59,7 +60,7 @@ const {
5960
const EE = require('node:events')
6061
const { Readable, pipeline } = require('node:stream')
6162
const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor, bufferToLowerCasedHeaderName } = require('../core/util')
62-
const { dataURLProcessor, serializeAMimeType, parseMIMEType } = require('./dataURL')
63+
const { dataURLProcessor, serializeAMimeType, minimizeSupportedMimeType } = require('./dataURL')
6364
const { getGlobalDispatcher } = require('../global')
6465
const { webidl } = require('./webidl')
6566
const { STATUS_CODES } = require('node:http')
@@ -1027,11 +1028,11 @@ function fetchFinale (fetchParams, response) {
10271028
responseStatus = response.status
10281029

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

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

0 commit comments

Comments
 (0)