aboutsummaryrefslogtreecommitdiffstats
path: root/vanilla/node_modules/undici/lib/core/request.js
diff options
context:
space:
mode:
authorAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
committerAdam Mathes <adam@adammathes.com>2026-02-13 21:34:48 -0800
commit76cb9c2a39d477a64824a985ade40507e3bbade1 (patch)
tree41e997aa9c6f538d3a136af61dae9424db2005a9 /vanilla/node_modules/undici/lib/core/request.js
parent819a39a21ac992b1393244a4c283bbb125208c69 (diff)
downloadneko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.gz
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.tar.bz2
neko-76cb9c2a39d477a64824a985ade40507e3bbade1.zip
feat(vanilla): add testing infrastructure and tests (NK-wjnczv)
Diffstat (limited to 'vanilla/node_modules/undici/lib/core/request.js')
-rw-r--r--vanilla/node_modules/undici/lib/core/request.js412
1 files changed, 412 insertions, 0 deletions
diff --git a/vanilla/node_modules/undici/lib/core/request.js b/vanilla/node_modules/undici/lib/core/request.js
new file mode 100644
index 0000000..7dbf781
--- /dev/null
+++ b/vanilla/node_modules/undici/lib/core/request.js
@@ -0,0 +1,412 @@
+'use strict'
+
+const {
+ InvalidArgumentError,
+ NotSupportedError
+} = require('./errors')
+const assert = require('node:assert')
+const {
+ isValidHTTPToken,
+ isValidHeaderValue,
+ isStream,
+ destroy,
+ isBuffer,
+ isFormDataLike,
+ isIterable,
+ isBlobLike,
+ serializePathWithQuery,
+ assertRequestHandler,
+ getServerName,
+ normalizedMethodRecords,
+ getProtocolFromUrlString
+} = 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')
+
+class Request {
+ constructor (origin, {
+ path,
+ method,
+ body,
+ headers,
+ query,
+ idempotent,
+ blocking,
+ upgrade,
+ headersTimeout,
+ bodyTimeout,
+ reset,
+ expectContinue,
+ servername,
+ throwOnError,
+ maxRedirections
+ }, handler) {
+ if (typeof path !== 'string') {
+ throw new InvalidArgumentError('path must be a string')
+ } else if (
+ path[0] !== '/' &&
+ !(path.startsWith('http://') || path.startsWith('https://')) &&
+ method !== 'CONNECT'
+ ) {
+ throw new InvalidArgumentError('path must be an absolute URL or start with a slash')
+ } 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 (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) {
+ throw new InvalidArgumentError('invalid request method')
+ }
+
+ if (upgrade && typeof upgrade !== 'string') {
+ throw new InvalidArgumentError('upgrade must be a string')
+ }
+
+ if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) {
+ throw new InvalidArgumentError('invalid headersTimeout')
+ }
+
+ if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) {
+ throw new InvalidArgumentError('invalid bodyTimeout')
+ }
+
+ if (reset != null && typeof reset !== 'boolean') {
+ throw new InvalidArgumentError('invalid reset')
+ }
+
+ if (expectContinue != null && typeof expectContinue !== 'boolean') {
+ throw new InvalidArgumentError('invalid expectContinue')
+ }
+
+ if (throwOnError != null) {
+ throw new InvalidArgumentError('invalid throwOnError')
+ }
+
+ if (maxRedirections != null && maxRedirections !== 0) {
+ throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
+ }
+
+ this.headersTimeout = headersTimeout
+
+ this.bodyTimeout = bodyTimeout
+
+ this.method = method
+
+ this.abort = null
+
+ if (body == null) {
+ this.body = null
+ } else if (isStream(body)) {
+ this.body = body
+
+ const rState = this.body._readableState
+ if (!rState || !rState.autoDestroy) {
+ this.endHandler = function autoDestroy () {
+ destroy(this)
+ }
+ this.body.on('end', this.endHandler)
+ }
+
+ this.errorHandler = err => {
+ if (this.abort) {
+ this.abort(err)
+ } else {
+ this.error = err
+ }
+ }
+ this.body.on('error', this.errorHandler)
+ } 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
+ } else if (body instanceof ArrayBuffer) {
+ this.body = body.byteLength ? Buffer.from(body) : null
+ } else if (typeof body === 'string') {
+ this.body = body.length ? Buffer.from(body) : null
+ } 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')
+ }
+
+ this.completed = false
+ this.aborted = false
+
+ this.upgrade = upgrade || null
+
+ this.path = query ? serializePathWithQuery(path, query) : path
+
+ // TODO: shall we maybe standardize it to an URL object?
+ this.origin = origin
+
+ this.protocol = getProtocolFromUrlString(origin)
+
+ this.idempotent = idempotent == null
+ ? method === 'HEAD' || method === 'GET'
+ : idempotent
+
+ this.blocking = blocking ?? this.method !== 'HEAD'
+
+ this.reset = reset == null ? null : reset
+
+ this.host = null
+
+ this.contentLength = null
+
+ this.contentType = null
+
+ this.headers = []
+
+ // Only for H2
+ this.expectContinue = expectContinue != null ? expectContinue : false
+
+ 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(this, headers[i], headers[i + 1])
+ }
+ } else if (headers && typeof headers === 'object') {
+ 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')
+ }
+
+ assertRequestHandler(handler, method, upgrade)
+
+ this.servername = servername || getServerName(this.host) || null
+
+ this[kHandler] = handler
+
+ if (channels.create.hasSubscribers) {
+ channels.create.publish({ request: this })
+ }
+ }
+
+ onBodySent (chunk) {
+ if (channels.bodyChunkSent.hasSubscribers) {
+ channels.bodyChunkSent.publish({ request: this, chunk })
+ }
+ if (this[kHandler].onBodySent) {
+ try {
+ return this[kHandler].onBodySent(chunk)
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+ }
+
+ onRequestSent () {
+ if (channels.bodySent.hasSubscribers) {
+ channels.bodySent.publish({ request: this })
+ }
+
+ if (this[kHandler].onRequestSent) {
+ try {
+ return this[kHandler].onRequestSent()
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+ }
+
+ onConnect (abort) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (this.error) {
+ abort(this.error)
+ } else {
+ this.abort = abort
+ return this[kHandler].onConnect(abort)
+ }
+ }
+
+ onResponseStarted () {
+ return this[kHandler].onResponseStarted?.()
+ }
+
+ onHeaders (statusCode, headers, resume, statusText) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (channels.headers.hasSubscribers) {
+ channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
+ }
+
+ try {
+ return this[kHandler].onHeaders(statusCode, headers, resume, statusText)
+ } catch (err) {
+ this.abort(err)
+ }
+ }
+
+ onData (chunk) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ if (channels.bodyChunkReceived.hasSubscribers) {
+ channels.bodyChunkReceived.publish({ request: this, chunk })
+ }
+ try {
+ return this[kHandler].onData(chunk)
+ } catch (err) {
+ this.abort(err)
+ return false
+ }
+ }
+
+ onUpgrade (statusCode, headers, socket) {
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ return this[kHandler].onUpgrade(statusCode, headers, socket)
+ }
+
+ onComplete (trailers) {
+ this.onFinally()
+
+ assert(!this.aborted)
+ assert(!this.completed)
+
+ this.completed = true
+ if (channels.trailers.hasSubscribers) {
+ channels.trailers.publish({ request: this, trailers })
+ }
+
+ try {
+ return this[kHandler].onComplete(trailers)
+ } catch (err) {
+ // TODO (fix): This might be a bad idea?
+ this.onError(err)
+ }
+ }
+
+ onError (error) {
+ this.onFinally()
+
+ if (channels.error.hasSubscribers) {
+ channels.error.publish({ request: this, error })
+ }
+
+ if (this.aborted) {
+ return
+ }
+ this.aborted = true
+
+ return this[kHandler].onError(error)
+ }
+
+ onFinally () {
+ if (this.errorHandler) {
+ this.body.off('error', this.errorHandler)
+ this.errorHandler = null
+ }
+
+ if (this.endHandler) {
+ this.body.off('end', this.endHandler)
+ this.endHandler = null
+ }
+ }
+
+ addHeader (key, value) {
+ processHeader(this, key, value)
+ return this
+ }
+}
+
+function processHeader (request, key, val) {
+ if (val && (typeof val === 'object' && !Array.isArray(val))) {
+ throw new InvalidArgumentError(`invalid ${key} header`)
+ } else if (val === undefined) {
+ return
+ }
+
+ let headerName = headerNameLowerCasedRecord[key]
+
+ if (headerName === undefined) {
+ headerName = key.toLowerCase()
+ if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) {
+ throw new InvalidArgumentError('invalid header key')
+ }
+ }
+
+ 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 && headerName === 'host') {
+ if (typeof val !== 'string') {
+ throw new InvalidArgumentError('invalid host header')
+ }
+ // Consumed by Client
+ request.host = val
+ } 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 && headerName === 'content-type') {
+ request.contentType = val
+ 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')
+ }
+
+ if (value === 'close') {
+ request.reset = true
+ }
+ } else if (headerName === 'expect') {
+ throw new NotSupportedError('expect header not supported')
+ } else {
+ request.headers.push(key, val)
+ }
+}
+
+module.exports = Request