Comparing version 6.7.1 to 6.8.0
@@ -29,2 +29,3 @@ # Errors | ||
| `ResponseExceededMaxSizeError` | `UND_ERR_RES_EXCEEDED_MAX_SIZE` | response body exceed the max size allowed | | ||
| `SecureProxyConnectionError` | `UND_ERR_PRX_TLS` | tls connection to a proxy failed | | ||
@@ -31,0 +32,0 @@ ### `SocketError` |
@@ -94,3 +94,4 @@ 'use strict' | ||
const contentType = parsedHeaders['content-type'] | ||
const body = new Readable({ resume, abort, contentType, highWaterMark }) | ||
const contentLength = parsedHeaders['content-length'] | ||
const body = new Readable({ resume, abort, contentType, contentLength, highWaterMark }) | ||
@@ -97,0 +98,0 @@ this.callback = null |
@@ -14,4 +14,5 @@ // Ported from https://github.com/nodejs/undici/pull/907 | ||
const kBody = Symbol('kBody') | ||
const kAbort = Symbol('abort') | ||
const kAbort = Symbol('kAbort') | ||
const kContentType = Symbol('kContentType') | ||
const kContentLength = Symbol('kContentLength') | ||
@@ -25,2 +26,3 @@ const noop = () => {} | ||
contentType = '', | ||
contentLength, | ||
highWaterMark = 64 * 1024 // Same as nodejs fs streams. | ||
@@ -40,2 +42,3 @@ }) { | ||
this[kContentType] = contentType | ||
this[kContentLength] = contentLength | ||
@@ -152,3 +155,3 @@ // Is stream being consumed through Readable API? | ||
async dump (opts) { | ||
let limit = Number.isFinite(opts?.limit) ? opts.limit : 262144 | ||
let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024 | ||
const signal = opts?.signal | ||
@@ -167,2 +170,6 @@ | ||
return await new Promise((resolve, reject) => { | ||
if (this[kContentLength] > limit) { | ||
this.destroy(new AbortError()) | ||
} | ||
const onAbort = () => { | ||
@@ -169,0 +176,0 @@ this.destroy(signal.reason ?? new AbortError()) |
@@ -24,26 +24,64 @@ const assert = require('node:assert') | ||
const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}` | ||
if (statusCode === 204 || !contentType || !chunks) { | ||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) | ||
queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))) | ||
return | ||
} | ||
const stackTraceLimit = Error.stackTraceLimit | ||
Error.stackTraceLimit = 0 | ||
let payload | ||
try { | ||
if (contentType.startsWith('application/json')) { | ||
const payload = JSON.parse(chunksDecode(chunks, length)) | ||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) | ||
return | ||
if (isContentTypeApplicationJson(contentType)) { | ||
payload = JSON.parse(chunksDecode(chunks, length)) | ||
} else if (isContentTypeText(contentType)) { | ||
payload = chunksDecode(chunks, length) | ||
} | ||
if (contentType.startsWith('text/')) { | ||
const payload = chunksDecode(chunks, length) | ||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) | ||
return | ||
} | ||
} catch (err) { | ||
// Process in a fallback if error | ||
} catch { | ||
// process in a callback to avoid throwing in the microtask queue | ||
} finally { | ||
Error.stackTraceLimit = stackTraceLimit | ||
} | ||
queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload))) | ||
} | ||
process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) | ||
const isContentTypeApplicationJson = (contentType) => { | ||
return ( | ||
contentType.length > 15 && | ||
contentType[11] === '/' && | ||
contentType[0] === 'a' && | ||
contentType[1] === 'p' && | ||
contentType[2] === 'p' && | ||
contentType[3] === 'l' && | ||
contentType[4] === 'i' && | ||
contentType[5] === 'c' && | ||
contentType[6] === 'a' && | ||
contentType[7] === 't' && | ||
contentType[8] === 'i' && | ||
contentType[9] === 'o' && | ||
contentType[10] === 'n' && | ||
contentType[12] === 'j' && | ||
contentType[13] === 's' && | ||
contentType[14] === 'o' && | ||
contentType[15] === 'n' | ||
) | ||
} | ||
module.exports = { getResolveErrorBodyCallback } | ||
const isContentTypeText = (contentType) => { | ||
return ( | ||
contentType.length > 4 && | ||
contentType[4] === '/' && | ||
contentType[0] === 't' && | ||
contentType[1] === 'e' && | ||
contentType[2] === 'x' && | ||
contentType[3] === 't' | ||
) | ||
} | ||
module.exports = { | ||
getResolveErrorBodyCallback, | ||
isContentTypeApplicationJson, | ||
isContentTypeText | ||
} |
@@ -198,2 +198,12 @@ 'use strict' | ||
class SecureProxyConnectionError extends UndiciError { | ||
constructor (cause, message, options) { | ||
super(message, { cause, ...(options ?? {}) }) | ||
this.name = 'SecureProxyConnectionError' | ||
this.message = message || 'Secure Proxy Connection failed' | ||
this.code = 'UND_ERR_PRX_TLS' | ||
this.cause = cause | ||
} | ||
} | ||
module.exports = { | ||
@@ -220,3 +230,4 @@ AbortError, | ||
ResponseExceededMaxSizeError, | ||
RequestRetryError | ||
RequestRetryError, | ||
SecureProxyConnectionError | ||
} |
@@ -43,3 +43,4 @@ 'use strict' | ||
throwOnError, | ||
expectContinue | ||
expectContinue, | ||
servername | ||
}, handler) { | ||
@@ -185,3 +186,3 @@ if (typeof path !== 'string') { | ||
this.servername = getServerName(this.host) | ||
this.servername = servername || getServerName(this.host) | ||
@@ -188,0 +189,0 @@ this[kHandler] = handler |
@@ -443,3 +443,4 @@ 'use strict' | ||
const hasToWellFormed = !!String.prototype.toWellFormed | ||
const hasToWellFormed = typeof String.prototype.toWellFormed === 'function' | ||
const hasIsWellFormed = typeof String.prototype.isWellFormed === 'function' | ||
@@ -454,2 +455,10 @@ /** | ||
/** | ||
* @param {string} val | ||
*/ | ||
// TODO: move this to webidl | ||
function isUSVString (val) { | ||
return hasIsWellFormed ? `${val}`.isWellFormed() : toUSVString(val) === `${val}` | ||
} | ||
/** | ||
* @see https://tools.ietf.org/html/rfc7230#section-3.2.6 | ||
@@ -543,2 +552,3 @@ * @param {number} c | ||
toUSVString, | ||
isUSVString, | ||
isReadableAborted, | ||
@@ -545,0 +555,0 @@ isBlobLike, |
@@ -8,3 +8,3 @@ 'use strict' | ||
const DispatcherBase = require('./dispatcher-base') | ||
const { InvalidArgumentError, RequestAbortedError } = require('../core/errors') | ||
const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors') | ||
const buildConnector = require('../core/connect') | ||
@@ -41,6 +41,5 @@ | ||
const url = this.#getUrl(opts) | ||
const { href, origin, port, protocol, username, password } = url | ||
const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url | ||
this[kProxy] = { uri: href, protocol } | ||
this[kAgent] = new Agent(opts) | ||
this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) | ||
@@ -83,3 +82,4 @@ ? opts.interceptors.ProxyAgent | ||
host: requestedHost | ||
} | ||
}, | ||
servername: this[kProxyTls]?.servername || proxyHostname | ||
}) | ||
@@ -102,3 +102,8 @@ if (statusCode !== 200) { | ||
} catch (err) { | ||
callback(err) | ||
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') { | ||
// Throw a custom error to avoid loop in client.js#connect | ||
callback(new SecureProxyConnectionError(err)) | ||
} else { | ||
callback(err) | ||
} | ||
} | ||
@@ -105,0 +110,0 @@ } |
'use strict' | ||
const { webidl } = require('./webidl') | ||
const { toUSVString, isUSVString, bufferToLowerCasedHeaderName } = require('../../core/util') | ||
const { utf8DecodeBytes } = require('./util') | ||
@@ -9,3 +9,3 @@ const { HTTP_TOKEN_CODEPOINTS, isomorphicDecode } = require('./data-url') | ||
const assert = require('node:assert') | ||
const { isAscii, File: NodeFile } = require('node:buffer') | ||
const { File: NodeFile } = require('node:buffer') | ||
@@ -20,2 +20,14 @@ const File = globalThis.File ?? NodeFile ?? UndiciFile | ||
/** | ||
* @param {string} chars | ||
*/ | ||
function isAsciiString (chars) { | ||
for (let i = 0; i < chars.length; ++i) { | ||
if ((chars.charCodeAt(i) & ~0x7F) !== 0) { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
/** | ||
* @see https://andreubotella.github.io/multipart-form-data/#multipart-form-data-boundary | ||
@@ -35,3 +47,3 @@ * @param {string} boundary | ||
// 0x2D (-) or 0x5F (_). | ||
for (let i = 0; i < boundary.length; i++) { | ||
for (let i = 0; i < length; ++i) { | ||
const cp = boundary.charCodeAt(i) | ||
@@ -64,3 +76,3 @@ | ||
// 1.1. Set name to the result of converting name into a scalar value string. | ||
name = webidl.converters.USVString(name) | ||
name = toUSVString(name) | ||
} else { | ||
@@ -70,3 +82,3 @@ // 2. Otherwise: | ||
// 2.1. Assert: name is a scalar value string. | ||
assert(name === webidl.converters.USVString(name)) | ||
assert(isUSVString(name)) | ||
@@ -102,10 +114,12 @@ // 2.2. Replace every occurrence of U+000D (CR) not followed by U+000A (LF), | ||
const boundaryString = mimeType.parameters.get('boundary') | ||
// 2. If mimeType’s parameters["boundary"] does not exist, return failure. | ||
// Otherwise, let boundary be the result of UTF-8 decoding mimeType’s | ||
// parameters["boundary"]. | ||
if (!mimeType.parameters.has('boundary')) { | ||
if (boundaryString === undefined) { | ||
return 'failure' | ||
} | ||
const boundary = Buffer.from(`--${mimeType.parameters.get('boundary')}`, 'utf8') | ||
const boundary = Buffer.from(`--${boundaryString}`, 'utf8') | ||
@@ -209,3 +223,6 @@ // 3. Let entry list be an empty entry list. | ||
// 5.10.2. If contentType is not an ASCII string, set contentType to the empty string. | ||
if (!isAscii(Buffer.from(contentType))) { | ||
// Note: `buffer.isAscii` can be used at zero-cost, but converting a string to a buffer is a high overhead. | ||
// Content-Type is a relatively small string, so it is faster to use `String#charCodeAt`. | ||
if (!isAsciiString(contentType)) { | ||
contentType = '' | ||
@@ -224,4 +241,4 @@ } | ||
// 5.12. Assert: name is a scalar value string and value is either a scalar value string or a File object. | ||
assert(name === webidl.converters.USVString(name)) | ||
assert((typeof value === 'string' && value === webidl.converters.USVString(value)) || isFileLike(value)) | ||
assert(isUSVString(name)) | ||
assert((typeof value === 'string' && isUSVString(value)) || isFileLike(value)) | ||
@@ -291,3 +308,3 @@ // 5.13. Create an entry with name and value, and append it to entry list. | ||
// 2.8. Byte-lowercase header name and switch on the result: | ||
switch (new TextDecoder().decode(headerName).toLowerCase()) { | ||
switch (bufferToLowerCasedHeaderName(headerName)) { | ||
case 'content-disposition': { | ||
@@ -440,6 +457,5 @@ // 1. Set name and filename to null. | ||
const result = [] | ||
let index = 0 | ||
while (position.position < input.length && condition(input[position.position])) { | ||
result[index++] = input[position.position] | ||
result.push(input[position.position]) | ||
@@ -446,0 +462,0 @@ position.position++ |
@@ -15,2 +15,3 @@ // https://github.com/Ethan-Arrowood/undici-fetch | ||
const assert = require('node:assert') | ||
const util = require('util') | ||
@@ -580,4 +581,14 @@ const kHeadersMap = Symbol('headers map') | ||
} | ||
[util.inspect.custom] (depth, options) { | ||
const inspected = util.inspect(this[kHeadersList].entries) | ||
return `Headers ${inspected}` | ||
} | ||
} | ||
Object.defineProperty(Headers.prototype, util.inspect.custom, { | ||
enumerable: false | ||
}) | ||
iteratorMixin('Headers', Headers, kHeadersSortedMap, 0, 1) | ||
@@ -584,0 +595,0 @@ |
{ | ||
"name": "undici", | ||
"version": "6.7.1", | ||
"version": "6.8.0", | ||
"description": "An HTTP/1.1 client, written from scratch for Node.js", | ||
@@ -110,2 +110,3 @@ "homepage": "https://undici.nodejs.org", | ||
"jsfuzz": "^1.0.15", | ||
"node-forge": "^1.3.1", | ||
"pre-commit": "^1.2.2", | ||
@@ -112,0 +113,0 @@ "proxy": "^2.1.1", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1117416
23262
23