Socket
Socket
Sign inDemoInstall

undici

Package Overview
Dependencies
Maintainers
3
Versions
212
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

undici - npm Package Compare versions

Comparing version 4.3.1 to 4.4.1

lib/fetch/body.js

13

index.js

@@ -17,2 +17,4 @@ 'use strict'

const nodeMajor = Number(process.versions.node.split('.')[0])
Object.assign(Dispatcher.prototype, api)

@@ -88,2 +90,13 @@

if (nodeMajor >= 16) {
const fetchImpl = require('./lib/fetch')
module.exports.fetch = async function fetch (resource, init) {
const dispatcher = getGlobalDispatcher()
return fetchImpl.call(dispatcher, resource, init)
}
module.exports.Headers = require('./lib/fetch/headers').Headers
module.exports.Response = require('./lib/fetch/response').Response
module.exports.Request = require('./lib/fetch/request').Request
}
module.exports.request = makeDispatcher(api.request)

@@ -90,0 +103,0 @@ module.exports.stream = makeDispatcher(api.stream)

5

lib/api/api-request.js

@@ -74,3 +74,4 @@ 'use strict'

const body = new Readable(resume, abort)
const parsedHeaders = util.parseHeaders(headers)
const body = new Readable(resume, abort, parsedHeaders['content-type'])

@@ -82,3 +83,3 @@ this.callback = null

statusCode,
headers: util.parseHeaders(headers),
headers: parsedHeaders,
trailers: this.trailers,

@@ -85,0 +86,0 @@ opaque,

25

lib/api/readable.js

@@ -8,2 +8,3 @@ // Ported from https://github.com/nodejs/undici/pull/907

const { RequestAbortedError, NotSupportedError } = require('../core/errors')
const util = require('../core/util')

@@ -16,5 +17,6 @@ let Blob

const kAbort = Symbol('abort')
const kContentType = Symbol('kContentType')
module.exports = class BodyReadable extends Readable {
constructor (resume, abort) {
constructor (resume, abort, contentType = '') {
super({ autoDestroy: true, read: resume })

@@ -27,2 +29,3 @@

this[kBody] = null
this[kContentType] = contentType

@@ -127,3 +130,3 @@ // Is stream being consumed through Readable API?

get bodyUsed () {
return isDisturbed(this)
return util.isDisturbed(this)
}

@@ -134,3 +137,3 @@

if (!this[kBody]) {
this[kBody] = Readable.toWeb(this)
this[kBody] = util.toWeb(this)
if (this[kConsume]) {

@@ -152,17 +155,5 @@ // TODO: Is this the best way to force a lock?

// https://streams.spec.whatwg.org/#readablestream-disturbed
function isDisturbed (self) {
// Waiting for: https://github.com/nodejs/node/pull/39589
const { _readableState: state } = self
return !!(
state.dataEmitted ||
state.endEmitted ||
state.errorEmitted ||
state.closeEmitted
)
}
// https://fetch.spec.whatwg.org/#body-unusable
function isUnusable (self) {
return isDisturbed(self) || isLocked(self)
return util.isDisturbed(self) || isLocked(self)
}

@@ -249,3 +240,3 @@

}
resolve(new Blob(body))
resolve(new Blob(body, { type: stream[kContentType] }))
}

@@ -252,0 +243,0 @@

@@ -265,3 +265,3 @@ 'use strict'

const { maxRedirections = this[kMaxRedirections] } = opts
if (maxRedirections != null) {
if (maxRedirections) {
handler = new RedirectHandler(this, maxRedirections, opts, handler)

@@ -275,3 +275,3 @@ }

// Do nothing.
} else if (util.isStream(request.body) || util.isStrictIterable(request.body)) {
} else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) {
// Wait a tick in case stream/iterator is ended in the same tick.

@@ -411,3 +411,6 @@ this[kResuming] = 1

wasm_on_status: (p, at, len) => {
return 0
assert.strictEqual(currentParser.ptr, p)
const start = at - currentBufferPtr
const end = start + len
return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
},

@@ -469,2 +472,3 @@ wasm_on_message_begin: (p) => {

this.statusCode = null
this.statusText = ''
this.upgrade = false

@@ -530,6 +534,16 @@ this.headers = []

this.paused = false
this.socket.resume()
this.execute(EMPTY_BUF) // Flush parser.
this.execute(this.socket.read() || EMPTY_BUF) // Flush parser.
this.readMore()
}
readMore () {
while (!this.paused && this.ptr) {
const chunk = this.socket.read()
if (chunk === null) {
break
}
this.execute(chunk)
}
}
execute (data) {

@@ -577,3 +591,2 @@ assert(this.ptr != null)

this.paused = true
socket.pause()
socket.unshift(data.slice(offset))

@@ -653,2 +666,6 @@ } else if (ret !== constants.ERROR.OK) {

onStatus (buf) {
this.statusText = buf.toString()
}
onMessageBegin () {

@@ -719,3 +736,2 @@ const { socket, client } = this

assert(socket === client[kSocket])
assert(!socket.isPaused())
assert(!this.paused)

@@ -725,2 +741,3 @@ assert(request.upgrade || request.method === 'CONNECT')

this.statusCode = null
this.statusText = ''
this.shouldKeepAlive = null

@@ -732,12 +749,2 @@

// _readableState.flowing might be `true` if the socket has been
// explicitly `resume()`:d even if we never registered a 'data'
// listener.
// We need to stop unshift from emitting 'data'. However, we cannot
// call pause() as that will stop socket from automatically resuming
// when 'data' listener is registered.
// Reset socket state to non flowing:
socket._readableState.flowing = null
socket.unshift(head)

@@ -752,3 +759,3 @@

.removeListener('error', onSocketError)
.removeListener('data', onSocketData)
.removeListener('readable', onSocketReadable)
.removeListener('end', onSocketEnd)

@@ -771,3 +778,3 @@ .removeListener('close', onSocketClose)

onHeadersComplete (statusCode, upgrade, shouldKeepAlive) {
const { client, socket, headers } = this
const { client, socket, headers, statusText } = this

@@ -859,3 +866,3 @@ /* istanbul ignore next: difficult to make a test case for */

try {
if (request.onHeaders(statusCode, headers, this.resume) === false) {
if (request.onHeaders(statusCode, headers, this.resume, statusText) === false) {
return constants.ERROR.PAUSED

@@ -927,2 +934,3 @@ }

this.statusCode = null
this.statusText = ''
this.bytesRead = 0

@@ -1012,5 +1020,5 @@ this.contentLength = ''

function onSocketData (data) {
function onSocketReadable (data) {
const { [kParser]: parser } = this
parser.execute(data)
parser.readMore()
}

@@ -1156,3 +1164,3 @@

.on('error', onSocketError)
.on('data', onSocketData)
.on('readable', onSocketReadable)
.on('end', onSocketEnd)

@@ -1308,3 +1316,3 @@ .on('close', onSocketClose)

if (client[kRunning] > 0 &&
(util.isStream(request.body) || util.isStrictIterable(request.body))) {
(util.isStream(request.body) || util.isAsyncIterable(request.body))) {
// Request with stream or iterator body can error while other requests

@@ -1441,3 +1449,3 @@ // are inflight and indirectly error those as well.

} else if (util.isBuffer(body)) {
assert(contentLength !== null, 'buffer body must have content length')
assert(contentLength === body.byteLength, 'buffer body must have content length')

@@ -1452,5 +1460,7 @@ socket.cork()

}
} else if (util.isBlob(body)) {
writeBlob({ client, request, socket, contentLength, header, expectsPayload })
} else if (util.isStream(body)) {
writeStream({ client, request, socket, contentLength, header, expectsPayload })
} else if (util.isStrictIterable(body)) {
} else if (util.isIterable(body)) {
writeIterable({ client, request, socket, contentLength, header, expectsPayload })

@@ -1542,2 +1552,31 @@ } else {

async function writeBlob ({ client, request, socket, contentLength, header, expectsPayload }) {
const { body } = request
assert(contentLength === body.size, 'blob body must have content length')
try {
if (contentLength != null && contentLength !== body.size) {
throw new RequestContentLengthMismatchError()
}
const buffer = Buffer.from(await body.arrayBuffer())
socket.cork()
socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'ascii')
socket.write(buffer)
socket.uncork()
request.onBodySent(buffer)
if (!expectsPayload) {
socket[kReset] = true
}
resume(client)
} catch (err) {
util.destroy(socket, err)
}
}
async function writeIterable ({ client, request, socket, contentLength, header, expectsPayload }) {

@@ -1544,0 +1583,0 @@ const { body } = request

'use strict'
class AbortError extends Error {
constructor () {
super('The operation was aborted')
this.code = 'ABORT_ERR'
this.name = 'AbortError'
}
}
class UndiciError extends Error {

@@ -171,3 +179,31 @@ constructor (message) {

class InvalidHTTPTokenError extends TypeError {
constructor (name, token) {
super(`${name} must be a valid HTTP token ["${token}"]`)
Error.captureStackTrace(this, InvalidHTTPTokenError)
this.name = 'InvalidHTTPToken'
this.code = 'INVALID_HTTP_TOKEN'
}
}
class HTTPInvalidHeaderValueError extends TypeError {
constructor (name, value) {
super(`Invalid value "${value}" for header "${name}"`)
Error.captureStackTrace(this, HTTPInvalidHeaderValueError)
this.name = 'HTTPInvalidHeaderValue'
this.code = 'HTTP_INVALID_HEADER_VALUE'
}
}
class InvalidThisError extends TypeError {
constructor (type) {
super(`Value of "this" must be of type ${type}`)
Error.captureStackTrace(this, InvalidThisError)
this.name = 'InvalidThis'
this.code = 'INVALID_THIS'
}
}
module.exports = {
AbortError,
HTTPParserError,

@@ -189,3 +225,6 @@ UndiciError,

NotSupportedError,
ResponseContentLengthMismatchError
ResponseContentLengthMismatchError,
InvalidHTTPTokenError,
HTTPInvalidHeaderValueError,
InvalidThisError
}

@@ -53,9 +53,14 @@ 'use strict'

this.body = null
} else if (util.isReadable(body)) {
} else if (util.isStream(body)) {
this.body = body
} else if (body instanceof DataView) {
// TODO: Why is DataView special?
this.body = body.buffer.byteLength ? Buffer.from(body.buffer) : null
} else if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) {
this.body = body.byteLength ? Buffer.from(body) : null
} else if (util.isBuffer(body)) {
this.body = body.length ? body : null
this.body = body.byteLength ? body : null
} else if (typeof body === 'string') {
this.body = body.length ? Buffer.from(body) : null
} else if (util.isIterable(body)) {
} else if (util.isIterable(body) || util.isBlob(body)) {
this.body = body

@@ -82,2 +87,4 @@ } else {

this.contentType = null
this.headers = ''

@@ -102,2 +109,7 @@

if (util.isBlob(body) && this.contentType == null) {
this.contentType = body.type
this.headers += `content-type: ${body.type}\r\n`
}
util.validateHandler(handler, method, upgrade)

@@ -127,7 +139,7 @@

onHeaders (statusCode, headers, resume) {
onHeaders (statusCode, headers, resume, statusText) {
assert(!this.aborted)
assert(!this.completed)
return this[kHandler].onHeaders(statusCode, headers, resume)
return this[kHandler].onHeaders(statusCode, headers, resume, statusText)
}

@@ -189,2 +201,9 @@

} else if (
request.contentType === null &&
key.length === 12 &&
key.toLowerCase() === 'content-type'
) {
request.contentType = val
request.headers += `${key}: ${val}\r\n`
} else if (
key.length === 17 &&

@@ -191,0 +210,0 @@ key.toLowerCase() === 'transfer-encoding'

@@ -8,2 +8,3 @@ module.exports = {

kConnecting: Symbol('connecting'),
kHeadersList: Symbol('headers list'),
kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'),

@@ -19,2 +20,3 @@ kKeepAliveMaxTimeout: Symbol('max keep alive timeout'),

kNoRef: Symbol('no ref'),
kBodyUsed: Symbol('used'),
kRunning: Symbol('running'),

@@ -21,0 +23,0 @@ kPending: Symbol('pending'),

'use strict'
const assert = require('assert')
const { kDestroyed } = require('./symbols')
const { kDestroyed, kBodyUsed } = require('./symbols')
const { IncomingMessage } = require('http')
const stream = require('stream')
const net = require('net')
const { InvalidArgumentError } = require('./errors')
const { Blob } = require('buffer')
function nop () {}
function isReadable (obj) {
return !!(obj && typeof obj.pipe === 'function' &&
typeof obj.on === 'function')
function isStream (obj) {
return obj && typeof obj.pipe === 'function'
}
function isWritable (obj) {
return !!(obj && typeof obj.write === 'function' &&
typeof obj.on === 'function')
function isBlob (obj) {
return obj && Blob && obj instanceof Blob
}
function isStream (obj) {
return isReadable(obj) || isWritable(obj)
}
function parseURL (url) {

@@ -114,2 +110,6 @@ if (typeof url === 'string') {

function isAsyncIterable (obj) {
return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function')
}
function isIterable (obj) {

@@ -119,8 +119,6 @@ return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function'))

function isStrictIterable (obj) {
return obj && !isStream(obj) && typeof obj !== 'string' && !isBuffer(obj) && isIterable(obj)
}
function bodyLength (body) {
if (body && typeof body.on === 'function') {
if (body == null) {
return 0
} else if (isStream(body)) {
const state = body._readableState

@@ -130,9 +128,9 @@ return state && state.ended === true && Number.isFinite(state.length)

: null
} else if (isStrictIterable(body)) {
return null
} else if (isBlob(body)) {
return body.size
} else if (isBuffer(body)) {
return body.byteLength
}
assert(!body || Number.isFinite(body.byteLength))
return body ? body.byteLength : 0
return null
}

@@ -144,2 +142,7 @@

function isAborted (stream) {
const state = stream && stream._readableState
return isDestroyed(stream) && state && !state.endEmitted
}
function destroy (stream, err) {

@@ -231,4 +234,22 @@ if (!isStream(stream) || isDestroyed(stream)) {

// A body is disturbed if it has been read from and it cannot
// be re-used without losing state or data.
function isDisturbed (body) {
const state = body && body._readableState
return !!(body && (
(stream.isDisturbed && stream.isDisturbed(body)) ||
body[kBodyUsed] ||
body.readableDidRead || (state && state.dataEmitted) ||
isAborted(stream)
))
}
const kEnumerableProperty = Object.create(null)
kEnumerableProperty.enumerable = true
module.exports = {
kEnumerableProperty,
nop,
isDisturbed,
isAborted,
parseOrigin,

@@ -238,5 +259,4 @@ parseURL,

isStream,
isReadable,
isIterable,
isStrictIterable,
isAsyncIterable,
isDestroyed,

@@ -249,3 +269,4 @@ parseHeaders,

isBuffer,
isBlob,
validateHandler
}
'use strict'
const util = require('../core/util')
const { kBodyUsed } = require('../core/symbols')
const assert = require('assert')

@@ -9,2 +10,17 @@ const { InvalidArgumentError } = require('../core/errors')

const kBody = Symbol('body')
class BodyAsyncIterable {
constructor (body) {
this[kBody] = body
this[kBodyUsed] = false
}
async * [Symbol.asyncIterator] () {
assert(!this[kBodyUsed], 'disturbed')
this[kBodyUsed] = true
yield * this[kBody]
}
}
class RedirectHandler {

@@ -26,14 +42,34 @@ constructor (dispatcher, maxRedirections, opts, handler) {

if (util.isStream(opts.body)) {
if (util.isStream(this.opts.body)) {
// TODO (fix): Provide some way for the user to cache the file to e.g. /tmp
// so that it can be dispatched again?
// TODO (fix): Do we need 100-expect support to provide a way to do this properly?
if (util.bodyLength(opts.body) === 0) {
opts.body
if (util.bodyLength(this.opts.body) === 0) {
this.opts.body
.on('data', function () {
assert(false)
})
} else {
this.maxRedirections = 0
}
if (typeof this.opts.body.readableDidRead !== 'boolean') {
this.opts.body[kBodyUsed] = false
// TODO (fix): Don't mutate readable state...
this.opts.body.on('data', function () {
this[kBodyUsed] = true
})
}
} else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') {
// TODO (fix): We can't access ReadableStream internal state
// to determine whether or not it has been disturbed. This is just
// a workaround.
this.opts.body = new BodyAsyncIterable(this.opts.body)
} else if (
this.opts.body &&
typeof this.opts.body !== 'string' &&
!ArrayBuffer.isView(this.opts.body) &&
util.isIterable(this.opts.body)
) {
// TODO: Should we allow re-using iterable if !this.opts.idempotent
// or through some other flag?
this.opts.body = new BodyAsyncIterable(this.opts.body)
}

@@ -55,4 +91,4 @@ }

onHeaders (statusCode, headers, resume) {
this.location = this.history.length >= this.maxRedirections
onHeaders (statusCode, headers, resume, statusText) {
this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body)
? null

@@ -62,3 +98,3 @@ : parseLocation(statusCode, headers)

if (!this.location) {
return this.handler.onHeaders(statusCode, headers, resume)
return this.handler.onHeaders(statusCode, headers, resume, statusText)
}

@@ -65,0 +101,0 @@

{
"name": "undici",
"version": "4.3.1",
"version": "4.4.1",
"description": "An HTTP/1.1 client, written from scratch for Node.js",

@@ -36,3 +36,6 @@ "homepage": "https://undici.nodejs.org",

"lint:fix": "standard --fix | snazzy",
"test": "tap test/*.js --no-coverage && jest test/jest/test",
"test": "tap test/*.js --no-coverage && mocha test/node-fetch && jest test/jest/test",
"test:node-fetch": "node scripts/test-node-fetch.js 16 && mocha test/node-fetch || echo Skipping",
"test:jest": "jest test/jest/test",
"test:tap": "tap test/*.js --no-coverage ",
"test:tdd": "tap test/*.js -w --no-coverage-report",

@@ -54,4 +57,10 @@ "test:typescript": "tsd",

"abort-controller": "^3.0.0",
"busboy": "^0.3.1",
"chai": "^4.3.4",
"chai-as-promised": "^7.1.1",
"chai-iterator": "^3.0.2",
"chai-string": "^1.5.0",
"concurrently": "^6.1.0",
"cronometro": "^0.8.0",
"delay": "^5.0.0",
"docsify-cli": "^4.4.2",

@@ -62,2 +71,4 @@ "https-pem": "^2.0.0",

"jsfuzz": "^1.0.15",
"mocha": "^9.0.3",
"p-timeout": "^3.2.0",
"pre-commit": "^1.2.2",

@@ -78,2 +89,5 @@ "proxy": "^1.0.2",

"standard": {
"env": [
"mocha"
],
"ignore": [

@@ -80,0 +94,0 @@ "lib/llhttp/constants.js",

@@ -158,2 +158,13 @@ # undici

### `undici.fetch(input[, init]): Promise`
Implements [fetch](https://fetch.spec.whatwg.org/).
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
https://fetch.spec.whatwg.org/#fetch-method
Only supported on Node 16+.
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant the Fetch Standard. We plan to ship breaking changes to this feature until it is out of experimental.
### `undici.upgrade([url, options]): Promise`

@@ -232,2 +243,3 @@

* [__Ethan Arrowood__](https://github.com/ethan-arrowood), <https://www.npmjs.com/~ethan_arrowood>
* [__Matteo Collina__](https://github.com/mcollina), <https://www.npmjs.com/~matteo.collina>

@@ -234,0 +246,0 @@ * [__Robert Nagy__](https://github.com/ronag), <https://www.npmjs.com/~ronag>

@@ -5,2 +5,3 @@ import { URL } from 'url'

import { IncomingHttpHeaders } from 'http'
import { Blob } from 'buffer'

@@ -43,3 +44,3 @@ type AbortSignal = unknown;

path: string;
method: string;
method: HttpMethod;
/** Default: `null` */

@@ -103,3 +104,3 @@ body?: string | Buffer | Uint8Array | Readable | null;

headers: IncomingHttpHeaders;
body: Readable;
body: Readable & BodyMixin;
trailers: Record<string, string>;

@@ -149,2 +150,16 @@ opaque: unknown;

export type PipelineHandler = (data: PipelineHandlerData) => Readable;
export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH';
/**
* @link https://fetch.spec.whatwg.org/#body-mixin
*/
interface BodyMixin {
readonly body?: never; // throws on node v16.6.0
readonly bodyUsed: boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<never>;
json(): Promise<any>;
text(): Promise<string>;
}
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc