aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/node_modules/undici/lib/core/request.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/undici/lib/core/request.js')
-rw-r--r--node_modules/undici/lib/core/request.js292
1 files changed, 94 insertions, 198 deletions
diff --git a/node_modules/undici/lib/core/request.js b/node_modules/undici/lib/core/request.js
index 3697e6a..7800303 100644
--- a/node_modules/undici/lib/core/request.js
+++ b/node_modules/undici/lib/core/request.js
@@ -4,52 +4,29 @@ const {
InvalidArgumentError,
NotSupportedError
} = require('./errors')
-const assert = require('assert')
-const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = require('./symbols')
-const util = require('./util')
-
-// tokenRegExp and headerCharRegex have been lifted from
-// https://github.com/nodejs/node/blob/main/lib/_http_common.js
-
-/**
- * Verifies that the given val is a valid HTTP token
- * per the rules defined in RFC 7230
- * See https://tools.ietf.org/html/rfc7230#section-3.2.6
- */
-const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
-
-/**
- * Matches if val contains an invalid field-vchar
- * field-value = *( field-content / obs-fold )
- * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
- * field-vchar = VCHAR / obs-text
- */
-const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
+const assert = require('node:assert')
+const {
+ isValidHTTPToken,
+ isValidHeaderValue,
+ isStream,
+ destroy,
+ isBuffer,
+ isFormDataLike,
+ isIterable,
+ isBlobLike,
+ buildURL,
+ validateHandler,
+ getServerName,
+ normalizedMethodRecords
+} = require('./util')
+const { channels } = require('./diagnostics.js')
+const { headerNameLowerCasedRecord } = require('./constants')
// Verifies that a given path is valid does not contain control chars \x00 to \x20
const invalidPathRegex = /[^\u0021-\u00ff]/
const kHandler = Symbol('handler')
-const channels = {}
-
-let extractBody
-
-try {
- const diagnosticsChannel = require('diagnostics_channel')
- channels.create = diagnosticsChannel.channel('undici:request:create')
- channels.bodySent = diagnosticsChannel.channel('undici:request:bodySent')
- channels.headers = diagnosticsChannel.channel('undici:request:headers')
- channels.trailers = diagnosticsChannel.channel('undici:request:trailers')
- channels.error = diagnosticsChannel.channel('undici:request:error')
-} catch {
- channels.create = { hasSubscribers: false }
- channels.bodySent = { hasSubscribers: false }
- channels.headers = { hasSubscribers: false }
- channels.trailers = { hasSubscribers: false }
- channels.error = { hasSubscribers: false }
-}
-
class Request {
constructor (origin, {
path,
@@ -64,7 +41,8 @@ class Request {
bodyTimeout,
reset,
throwOnError,
- expectContinue
+ expectContinue,
+ servername
}, handler) {
if (typeof path !== 'string') {
throw new InvalidArgumentError('path must be a string')
@@ -74,13 +52,13 @@ class Request {
method !== 'CONNECT'
) {
throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
- } else if (invalidPathRegex.exec(path) !== null) {
+ } else if (invalidPathRegex.test(path)) {
throw new InvalidArgumentError('invalid request path')
}
if (typeof method !== 'string') {
throw new InvalidArgumentError('method must be a string')
- } else if (tokenRegExp.exec(method) === null) {
+ } else if (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
throw new InvalidArgumentError('invalid request method')
}
@@ -116,13 +94,13 @@ class Request {
if (body == null) {
this.body = null
- } else if (util.isStream(body)) {
+ } else if (isStream(body)) {
this.body = body
const rState = this.body._readableState
if (!rState || !rState.autoDestroy) {
this.endHandler = function autoDestroy () {
- util.destroy(this)
+ destroy(this)
}
this.body.on('end', this.endHandler)
}
@@ -135,7 +113,7 @@ class Request {
}
}
this.body.on('error', this.errorHandler)
- } else if (util.isBuffer(body)) {
+ } else if (isBuffer(body)) {
this.body = body.byteLength ? body : null
} else if (ArrayBuffer.isView(body)) {
this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null
@@ -143,7 +121,7 @@ class Request {
this.body = body.byteLength ? Buffer.from(body) : null
} else if (typeof body === 'string') {
this.body = body.length ? Buffer.from(body) : null
- } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) {
+ } else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) {
this.body = body
} else {
throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable')
@@ -155,7 +133,7 @@ class Request {
this.upgrade = upgrade || null
- this.path = query ? util.buildURL(path, query) : path
+ this.path = query ? buildURL(path, query) : path
this.origin = origin
@@ -173,7 +151,7 @@ class Request {
this.contentType = null
- this.headers = ''
+ this.headers = []
// Only for H2
this.expectContinue = expectContinue != null ? expectContinue : false
@@ -186,39 +164,26 @@ class Request {
processHeader(this, headers[i], headers[i + 1])
}
} else if (headers && typeof headers === 'object') {
- const keys = Object.keys(headers)
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i]
- processHeader(this, key, headers[key])
+ if (headers[Symbol.iterator]) {
+ for (const header of headers) {
+ if (!Array.isArray(header) || header.length !== 2) {
+ throw new InvalidArgumentError('headers must be in key-value pair format')
+ }
+ processHeader(this, header[0], header[1])
+ }
+ } else {
+ const keys = Object.keys(headers)
+ for (let i = 0; i < keys.length; ++i) {
+ processHeader(this, keys[i], headers[keys[i]])
+ }
}
} else if (headers != null) {
throw new InvalidArgumentError('headers must be an object or an array')
}
- if (util.isFormDataLike(this.body)) {
- if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) {
- throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.')
- }
+ validateHandler(handler, method, upgrade)
- if (!extractBody) {
- extractBody = require('../fetch/body.js').extractBody
- }
-
- const [bodyStream, contentType] = extractBody(body)
- if (this.contentType == null) {
- this.contentType = contentType
- this.headers += `content-type: ${contentType}\r\n`
- }
- this.body = bodyStream.stream
- this.contentLength = bodyStream.length
- } else if (util.isBlobLike(body) && this.contentType == null && body.type) {
- this.contentType = body.type
- this.headers += `content-type: ${body.type}\r\n`
- }
-
- util.validateHandler(handler, method, upgrade)
-
- this.servername = util.getServerName(this.host)
+ this.servername = servername || getServerName(this.host)
this[kHandler] = handler
@@ -263,6 +228,10 @@ class Request {
}
}
+ onResponseStarted () {
+ return this[kHandler].onResponseStarted?.()
+ }
+
onHeaders (statusCode, headers, resume, statusText) {
assert(!this.aborted)
assert(!this.completed)
@@ -342,157 +311,84 @@ class Request {
}
}
- // TODO: adjust to support H2
addHeader (key, value) {
processHeader(this, key, value)
return this
}
-
- static [kHTTP1BuildRequest] (origin, opts, handler) {
- // TODO: Migrate header parsing here, to make Requests
- // HTTP agnostic
- return new Request(origin, opts, handler)
- }
-
- static [kHTTP2BuildRequest] (origin, opts, handler) {
- const headers = opts.headers
- opts = { ...opts, headers: null }
-
- const request = new Request(origin, opts, handler)
-
- request.headers = {}
-
- if (Array.isArray(headers)) {
- if (headers.length % 2 !== 0) {
- throw new InvalidArgumentError('headers array must be even')
- }
- for (let i = 0; i < headers.length; i += 2) {
- processHeader(request, headers[i], headers[i + 1], true)
- }
- } else if (headers && typeof headers === 'object') {
- const keys = Object.keys(headers)
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i]
- processHeader(request, key, headers[key], true)
- }
- } else if (headers != null) {
- throw new InvalidArgumentError('headers must be an object or an array')
- }
-
- return request
- }
-
- static [kHTTP2CopyHeaders] (raw) {
- const rawHeaders = raw.split('\r\n')
- const headers = {}
-
- for (const header of rawHeaders) {
- const [key, value] = header.split(': ')
-
- if (value == null || value.length === 0) continue
-
- if (headers[key]) headers[key] += `,${value}`
- else headers[key] = value
- }
-
- return headers
- }
}
-function processHeaderValue (key, val, skipAppend) {
- if (val && typeof val === 'object') {
+function processHeader (request, key, val) {
+ if (val && (typeof val === 'object' && !Array.isArray(val))) {
throw new InvalidArgumentError(`invalid ${key} header`)
+ } else if (val === undefined) {
+ return
}
- val = val != null ? `${val}` : ''
+ let headerName = headerNameLowerCasedRecord[key]
- if (headerCharRegex.exec(val) !== null) {
- throw new InvalidArgumentError(`invalid ${key} header`)
+ if (headerName === undefined) {
+ headerName = key.toLowerCase()
+ if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) {
+ throw new InvalidArgumentError('invalid header key')
+ }
}
- return skipAppend ? val : `${key}: ${val}\r\n`
-}
-
-function processHeader (request, key, val, skipAppend = false) {
- if (val && (typeof val === 'object' && !Array.isArray(val))) {
- throw new InvalidArgumentError(`invalid ${key} header`)
- } else if (val === undefined) {
- return
+ if (Array.isArray(val)) {
+ const arr = []
+ for (let i = 0; i < val.length; i++) {
+ if (typeof val[i] === 'string') {
+ if (!isValidHeaderValue(val[i])) {
+ throw new InvalidArgumentError(`invalid ${key} header`)
+ }
+ arr.push(val[i])
+ } else if (val[i] === null) {
+ arr.push('')
+ } else if (typeof val[i] === 'object') {
+ throw new InvalidArgumentError(`invalid ${key} header`)
+ } else {
+ arr.push(`${val[i]}`)
+ }
+ }
+ val = arr
+ } else if (typeof val === 'string') {
+ if (!isValidHeaderValue(val)) {
+ throw new InvalidArgumentError(`invalid ${key} header`)
+ }
+ } else if (val === null) {
+ val = ''
+ } else {
+ val = `${val}`
}
- if (
- request.host === null &&
- key.length === 4 &&
- key.toLowerCase() === 'host'
- ) {
- if (headerCharRegex.exec(val) !== null) {
- throw new InvalidArgumentError(`invalid ${key} header`)
+ if (request.host === null && headerName === 'host') {
+ if (typeof val !== 'string') {
+ throw new InvalidArgumentError('invalid host header')
}
// Consumed by Client
request.host = val
- } else if (
- request.contentLength === null &&
- key.length === 14 &&
- key.toLowerCase() === 'content-length'
- ) {
+ } else if (request.contentLength === null && headerName === 'content-length') {
request.contentLength = parseInt(val, 10)
if (!Number.isFinite(request.contentLength)) {
throw new InvalidArgumentError('invalid content-length header')
}
- } else if (
- request.contentType === null &&
- key.length === 12 &&
- key.toLowerCase() === 'content-type'
- ) {
+ } else if (request.contentType === null && headerName === 'content-type') {
request.contentType = val
- if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
- else request.headers += processHeaderValue(key, val)
- } else if (
- key.length === 17 &&
- key.toLowerCase() === 'transfer-encoding'
- ) {
- throw new InvalidArgumentError('invalid transfer-encoding header')
- } else if (
- key.length === 10 &&
- key.toLowerCase() === 'connection'
- ) {
+ request.headers.push(key, val)
+ } else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') {
+ throw new InvalidArgumentError(`invalid ${headerName} header`)
+ } else if (headerName === 'connection') {
const value = typeof val === 'string' ? val.toLowerCase() : null
if (value !== 'close' && value !== 'keep-alive') {
throw new InvalidArgumentError('invalid connection header')
- } else if (value === 'close') {
+ }
+
+ if (value === 'close') {
request.reset = true
}
- } else if (
- key.length === 10 &&
- key.toLowerCase() === 'keep-alive'
- ) {
- throw new InvalidArgumentError('invalid keep-alive header')
- } else if (
- key.length === 7 &&
- key.toLowerCase() === 'upgrade'
- ) {
- throw new InvalidArgumentError('invalid upgrade header')
- } else if (
- key.length === 6 &&
- key.toLowerCase() === 'expect'
- ) {
+ } else if (headerName === 'expect') {
throw new NotSupportedError('expect header not supported')
- } else if (tokenRegExp.exec(key) === null) {
- throw new InvalidArgumentError('invalid header key')
} else {
- if (Array.isArray(val)) {
- for (let i = 0; i < val.length; i++) {
- if (skipAppend) {
- if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}`
- else request.headers[key] = processHeaderValue(key, val[i], skipAppend)
- } else {
- request.headers += processHeaderValue(key, val[i])
- }
- }
- } else {
- if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend)
- else request.headers += processHeaderValue(key, val)
- }
+ request.headers.push(key, val)
}
}