| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent acfc33c commit aac97c2
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,10 +20,10 @@ import { createServer } from 'http' | |||
| 20 | 20 | import proxy from 'proxy' | |
| 21 | 21 | ||
| 22 | 22 | const server = await buildServer() | |
| 23 | - const proxy = await buildProxy() | ||
| 23 | + const proxyServer = await buildProxy() | ||
| 24 | 24 | ||
| 25 | 25 | const serverUrl = `http://localhost:${server.address().port}` | |
| 26 | - const proxyUrl = `http://localhost:${proxy.address().port}` | ||
| 26 | + const proxyUrl = `http://localhost:${proxyServer.address().port}` | ||
| 27 | 27 | ||
| 28 | 28 | server.on('request', (req, res) => { | |
| 29 | 29 | console.log(req.url) // '/hello?foo=bar' | |
@@ -47,7 +47,7 @@ console.log(response.statusCode) // 200 | |||
| 47 | 47 | console.log(JSON.parse(data)) // { hello: 'world' } | |
| 48 | 48 | ||
| 49 | 49 | server.close() | |
| 50 | - proxy.close() | ||
| 50 | + proxyServer.close() | ||
| 51 | 51 | client.close() | |
| 52 | 52 | ||
| 53 | 53 | function buildServer () { | |
@@ -73,12 +73,12 @@ import { createServer } from 'http' | |||
| 73 | 73 | import proxy from 'proxy' | |
| 74 | 74 | ||
| 75 | 75 | const server = await buildServer() | |
| 76 | - const proxy = await buildProxy() | ||
| 76 | + const proxyServer = await buildProxy() | ||
| 77 | 77 | ||
| 78 | 78 | const serverUrl = `http://localhost:${server.address().port}` | |
| 79 | - const proxyUrl = `http://localhost:${proxy.address().port}` | ||
| 79 | + const proxyUrl = `http://localhost:${proxyServer.address().port}` | ||
| 80 | 80 | ||
| 81 | - proxy.authenticate = function (req, fn) { | ||
| 81 | + proxyServer.authenticate = function (req, fn) { | ||
| 82 | 82 | fn(null, req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`) | |
| 83 | 83 | } | |
| 84 | 84 | ||
@@ -107,7 +107,7 @@ console.log(response.statusCode) // 200 | |||
| 107 | 107 | console.log(JSON.parse(data)) // { hello: 'world' } | |
| 108 | 108 | ||
| 109 | 109 | server.close() | |
| 110 | - proxy.close() | ||
| 110 | + proxyServer.close() | ||
| 111 | 111 | client.close() | |
| 112 | 112 | ||
| 113 | 113 | function buildServer () { | |
@@ -124,3 +124,4 @@ function buildProxy () { | |||
| 124 | 124 | }) | |
| 125 | 125 | } | |
| 126 | 126 | ``` | |
| 127 | + | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,6 +18,17 @@ const { parseOrigin } = require('./core/util') | |||
| 18 | 18 | const kFactory = Symbol('factory') | |
| 19 | 19 | ||
| 20 | 20 | const kOptions = Symbol('options') | |
| 21 | + const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor') | ||
| 22 | + const kCurrentWeight = Symbol('kCurrentWeight') | ||
| 23 | + const kIndex = Symbol('kIndex') | ||
| 24 | + const kWeight = Symbol('kWeight') | ||
| 25 | + const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') | ||
| 26 | + const kErrorPenalty = Symbol('kErrorPenalty') | ||
| 27 | + | ||
| 28 | + function getGreatestCommonDivisor (a, b) { | ||
| 29 | + if (b === 0) return a | ||
| 30 | + return getGreatestCommonDivisor(b, a % b) | ||
| 31 | + } | ||
| 21 | 32 | ||
| 22 | 33 | function defaultFactory (origin, opts) { | |
| 23 | 34 | return new Pool(origin, opts) | |
@@ -28,6 +39,11 @@ class BalancedPool extends PoolBase { | |||
| 28 | 39 | super() | |
| 29 | 40 | ||
| 30 | 41 | this[kOptions] = opts | |
| 42 | + this[kIndex] = -1 | ||
| 43 | + this[kCurrentWeight] = 0 | ||
| 44 | + | ||
| 45 | + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100 | ||
| 46 | + this[kErrorPenalty] = this[kOptions].errorPenalty || 15 | ||
| 31 | 47 | ||
| 32 | 48 | if (!Array.isArray(upstreams)) { | |
| 33 | 49 | upstreams = [upstreams] | |
@@ -42,6 +58,7 @@ class BalancedPool extends PoolBase { | |||
| 42 | 58 | for (const upstream of upstreams) { | |
| 43 | 59 | this.addUpstream(upstream) | |
| 44 | 60 | } | |
| 61 | + this._updateBalancedPoolStats() | ||
| 45 | 62 | } | |
| 46 | 63 | ||
| 47 | 64 | addUpstream (upstream) { | |
@@ -54,12 +71,40 @@ class BalancedPool extends PoolBase { | |||
| 54 | 71 | ))) { | |
| 55 | 72 | return this | |
| 56 | 73 | } | |
| 74 | + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) | ||
| 75 | + | ||
| 76 | + this[kAddClient](pool) | ||
| 77 | + pool.on('connect', () => { | ||
| 78 | + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]) | ||
| 79 | + }) | ||
| 80 | + | ||
| 81 | + pool.on('connectionError', () => { | ||
| 82 | + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) | ||
| 83 | + this._updateBalancedPoolStats() | ||
| 84 | + }) | ||
| 85 | + | ||
| 86 | + pool.on('disconnect', (...args) => { | ||
| 87 | + const err = args[2] | ||
| 88 | + if (err && err.code === 'UND_ERR_SOCKET') { | ||
| 89 | + // decrease the weight of the pool. | ||
| 90 | + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) | ||
| 91 | + this._updateBalancedPoolStats() | ||
| 92 | + } | ||
| 93 | + }) | ||
| 94 | + | ||
| 95 | + for (const client of this[kClients]) { | ||
| 96 | + client[kWeight] = this[kMaxWeightPerServer] | ||
| 97 | + } | ||
| 57 | 98 | ||
| 58 | - this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions]))) | ||
| 99 | + this._updateBalancedPoolStats() | ||
| 59 | 100 | ||
| 60 | 101 | return this | |
| 61 | 102 | } | |
| 62 | 103 | ||
| 104 | + _updateBalancedPoolStats () { | ||
| 105 | + this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0) | ||
| 106 | + } | ||
| 107 | + | ||
| 63 | 108 | removeUpstream (upstream) { | |
| 64 | 109 | const upstreamOrigin = parseOrigin(upstream).origin | |
| 65 | 110 | ||
@@ -100,10 +145,42 @@ class BalancedPool extends PoolBase { | |||
| 100 | 145 | return | |
| 101 | 146 | } | |
| 102 | 147 | ||
| 103 | - this[kClients].splice(this[kClients].indexOf(dispatcher), 1) | ||
| 104 | - this[kClients].push(dispatcher) | ||
| 148 | + const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true) | ||
| 149 | + | ||
| 150 | + if (allClientsBusy) { | ||
| 151 | + return | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + let counter = 0 | ||
| 155 | + | ||
| 156 | + let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain]) | ||
| 157 | + | ||
| 158 | + while (counter++ < this[kClients].length) { | ||
| 159 | + this[kIndex] = (this[kIndex] + 1) % this[kClients].length | ||
| 160 | + const pool = this[kClients][this[kIndex]] | ||
| 161 | + | ||
| 162 | + // find pool index with the largest weight | ||
| 163 | + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { | ||
| 164 | + maxWeightIndex = this[kIndex] | ||
| 165 | + } | ||
| 166 | + | ||
| 167 | + // decrease the current weight every `this[kClients].length`. | ||
| 168 | + if (this[kIndex] === 0) { | ||
| 169 | + // Set the current weight to the next lower weight. | ||
| 170 | + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor] | ||
| 171 | + | ||
| 172 | + if (this[kCurrentWeight] <= 0) { | ||
| 173 | + this[kCurrentWeight] = this[kMaxWeightPerServer] | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | + if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) { | ||
| 177 | + return pool | ||
| 178 | + } | ||
| 179 | + } | ||
| 105 | 180 | ||
| 106 | - return dispatcher | ||
| 181 | + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight] | ||
| 182 | + this[kIndex] = maxWeightIndex | ||
| 183 | + return this[kClients][maxWeightIndex] | ||
| 107 | 184 | } | |
| 108 | 185 | } | |
| 109 | 186 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -7,6 +7,27 @@ const { | |||
| 7 | 7 | const assert = require('assert') | |
| 8 | 8 | const util = require('./util') | |
| 9 | 9 | ||
| 10 | + // tokenRegExp and headerCharRegex have been lifted from | ||
| 11 | + // https://github.com/nodejs/node/blob/main/lib/_http_common.js | ||
| 12 | + | ||
| 13 | + /** | ||
| 14 | + * Verifies that the given val is a valid HTTP token | ||
| 15 | + * per the rules defined in RFC 7230 | ||
| 16 | + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 | ||
| 17 | + */ | ||
| 18 | + const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * Matches if val contains an invalid field-vchar | ||
| 22 | + * field-value = *( field-content / obs-fold ) | ||
| 23 | + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] | ||
| 24 | + * field-vchar = VCHAR / obs-text | ||
| 25 | + */ | ||
| 26 | + const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ | ||
| 27 | + | ||
| 28 | + // Verifies that a given path is valid does not contain control chars \x00 to \x20 | ||
| 29 | + const invalidPathRegex = /[^\u0021-\u00ff]/ | ||
| 30 | + | ||
| 10 | 31 | const kHandler = Symbol('handler') | |
| 11 | 32 | ||
| 12 | 33 | const channels = {} | |
@@ -54,10 +75,14 @@ class Request { | |||
| 54 | 75 | method !== 'CONNECT' | |
| 55 | 76 | ) { | |
| 56 | 77 | throw new InvalidArgumentError('path must be an absolute URL or start with a slash') | |
| 78 | + } else if (invalidPathRegex.exec(path) !== null) { | ||
| 79 | + throw new InvalidArgumentError('invalid request path') | ||
| 57 | 80 | } | |
| 58 | 81 | ||
| 59 | 82 | if (typeof method !== 'string') { | |
| 60 | 83 | throw new InvalidArgumentError('method must be a string') | |
| 84 | + } else if (tokenRegExp.exec(method) === null) { | ||
| 85 | + throw new InvalidArgumentError('invalid request method') | ||
| 61 | 86 | } | |
| 62 | 87 | ||
| 63 | 88 | if (upgrade && typeof upgrade !== 'string') { | |
@@ -301,6 +326,10 @@ function processHeader (request, key, val) { | |||
| 301 | 326 | key.toLowerCase() === 'expect' | |
| 302 | 327 | ) { | |
| 303 | 328 | throw new NotSupportedError('expect header not supported') | |
| 329 | + } else if (tokenRegExp.exec(key) === null) { | ||
| 330 | + throw new InvalidArgumentError('invalid header key') | ||
| 331 | + } else if (headerCharRegex.exec(val) !== null) { | ||
| 332 | + throw new InvalidArgumentError(`invalid ${key} header`) | ||
| 304 | 333 | } else { | |
| 305 | 334 | request.headers += `${key}: ${val}\r\n` | |
| 306 | 335 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -291,6 +291,10 @@ function bodyMixinMethods (instance) { | |||
| 291 | 291 | const chunks = [] | |
| 292 | 292 | ||
| 293 | 293 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 294 | + if (!isUint8Array(chunk)) { | ||
| 295 | + throw new TypeError('Expected Uint8Array chunk') | ||
| 296 | + } | ||
| 297 | + | ||
| 294 | 298 | // Assemble one final large blob with Uint8Array's can exhaust memory. | |
| 295 | 299 | // That's why we create create multiple blob's and using references | |
| 296 | 300 | chunks.push(new Blob([chunk])) | |
@@ -314,6 +318,10 @@ function bodyMixinMethods (instance) { | |||
| 314 | 318 | let offset = 0 | |
| 315 | 319 | ||
| 316 | 320 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 321 | + if (!isUint8Array(chunk)) { | ||
| 322 | + throw new TypeError('Expected Uint8Array chunk') | ||
| 323 | + } | ||
| 324 | + | ||
| 317 | 325 | buffer.set(chunk, offset) | |
| 318 | 326 | offset += chunk.length | |
| 319 | 327 | } | |
@@ -331,6 +339,10 @@ function bodyMixinMethods (instance) { | |||
| 331 | 339 | let size = 0 | |
| 332 | 340 | ||
| 333 | 341 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 342 | + if (!isUint8Array(chunk)) { | ||
| 343 | + throw new TypeError('Expected Uint8Array chunk') | ||
| 344 | + } | ||
| 345 | + | ||
| 334 | 346 | chunks.push(chunk) | |
| 335 | 347 | size += chunk.byteLength | |
| 336 | 348 | } | |
@@ -355,6 +367,10 @@ function bodyMixinMethods (instance) { | |||
| 355 | 367 | const textDecoder = new TextDecoder() | |
| 356 | 368 | ||
| 357 | 369 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 370 | + if (!isUint8Array(chunk)) { | ||
| 371 | + throw new TypeError('Expected Uint8Array chunk') | ||
| 372 | + } | ||
| 373 | + | ||
| 358 | 374 | result += textDecoder.decode(chunk, { stream: true }) | |
| 359 | 375 | } | |
| 360 | 376 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -186,7 +186,8 @@ function shouldRemoveHeader (header, removeContent, unknownOrigin) { | |||
| 186 | 186 | return ( | |
| 187 | 187 | (header.length === 4 && header.toString().toLowerCase() === 'host') || | |
| 188 | 188 | (removeContent && header.toString().toLowerCase().indexOf('content-') === 0) || | |
| 189 | - (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') | ||
| 189 | + (unknownOrigin && header.length === 13 && header.toString().toLowerCase() === 'authorization') || | ||
| 190 | + (unknownOrigin && header.length === 6 && header.toString().toLowerCase() === 'cookie') | ||
| 190 | 191 | ) | |
| 191 | 192 | } | |
| 192 | 193 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,7 +8,7 @@ const { | |||
| 8 | 8 | kOrigin, | |
| 9 | 9 | kGetNetConnect | |
| 10 | 10 | } = require('./mock-symbols') | |
| 11 | - const { buildURL } = require('../core/util') | ||
| 11 | + const { buildURL, nop } = require('../core/util') | ||
| 12 | 12 | ||
| 13 | 13 | function matchValue (match, value) { | |
| 14 | 14 | if (typeof match === 'string') { | |
@@ -288,6 +288,7 @@ function mockDispatch (opts, handler) { | |||
| 288 | 288 | const responseHeaders = generateKeyValues(headers) | |
| 289 | 289 | const responseTrailers = generateKeyValues(trailers) | |
| 290 | 290 | ||
| 291 | + handler.abort = nop | ||
| 291 | 292 | handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode)) | |
| 292 | 293 | handler.onData(Buffer.from(responseData)) | |
| 293 | 294 | handler.onComplete(responseTrailers) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,6 +1,6 @@ | |||
| 1 | 1 | { | |
| 2 | 2 | "name": "undici", | |
| 3 | - "version": "5.7.0", | ||
| 3 | + "version": "5.8.0", | ||
| 4 | 4 | "description": "An HTTP/1.1 client, written from scratch for Node.js", | |
| 5 | 5 | "homepage": "https://undici.nodejs.org", | |
| 6 | 6 | "bugs": { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2227,6 +2227,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | |||
| 2227 | 2227 | } | |
| 2228 | 2228 | const chunks = []; | |
| 2229 | 2229 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 2230 | + if (!isUint8Array(chunk)) { | ||
| 2231 | + throw new TypeError("Expected Uint8Array chunk"); | ||
| 2232 | + } | ||
| 2230 | 2233 | chunks.push(new Blob([chunk])); | |
| 2231 | 2234 | } | |
| 2232 | 2235 | return new Blob(chunks, { type: this.headers.get("Content-Type") || "" }); | |
@@ -2241,6 +2244,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | |||
| 2241 | 2244 | const buffer2 = new Uint8Array(contentLength); | |
| 2242 | 2245 | let offset2 = 0; | |
| 2243 | 2246 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 2247 | + if (!isUint8Array(chunk)) { | ||
| 2248 | + throw new TypeError("Expected Uint8Array chunk"); | ||
| 2249 | + } | ||
| 2244 | 2250 | buffer2.set(chunk, offset2); | |
| 2245 | 2251 | offset2 += chunk.length; | |
| 2246 | 2252 | } | |
@@ -2249,6 +2255,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | |||
| 2249 | 2255 | const chunks = []; | |
| 2250 | 2256 | let size = 0; | |
| 2251 | 2257 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 2258 | + if (!isUint8Array(chunk)) { | ||
| 2259 | + throw new TypeError("Expected Uint8Array chunk"); | ||
| 2260 | + } | ||
| 2252 | 2261 | chunks.push(chunk); | |
| 2253 | 2262 | size += chunk.byteLength; | |
| 2254 | 2263 | } | |
@@ -2267,6 +2276,9 @@ Content-Type: ${value.type || "application/octet-stream"}\r | |||
| 2267 | 2276 | let result = ""; | |
| 2268 | 2277 | const textDecoder = new TextDecoder(); | |
| 2269 | 2278 | for await (const chunk of consumeBody(this[kState].body)) { | |
| 2279 | + if (!isUint8Array(chunk)) { | ||
| 2280 | + throw new TypeError("Expected Uint8Array chunk"); | ||
| 2281 | + } | ||
| 2270 | 2282 | result += textDecoder.decode(chunk, { stream: true }); | |
| 2271 | 2283 | } | |
| 2272 | 2284 | result += textDecoder.decode(); | |
@@ -2350,6 +2362,9 @@ var require_request = __commonJS({ | |||
| 2350 | 2362 | } = require_errors(); | |
| 2351 | 2363 | var assert = require("assert"); | |
| 2352 | 2364 | var util = require_util(); | |
| 2365 | + var tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/; | ||
| 2366 | + var headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; | ||
| 2367 | + var invalidPathRegex = /[^\u0021-\u00ff]/; | ||
| 2353 | 2368 | var kHandler = Symbol("handler"); | |
| 2354 | 2369 | var channels = {}; | |
| 2355 | 2370 | var extractBody; | |
@@ -2388,9 +2403,13 @@ var require_request = __commonJS({ | |||
| 2388 | 2403 | throw new InvalidArgumentError("path must be a string"); | |
| 2389 | 2404 | } else if (path[0] !== "/" && !(path.startsWith("http://") || path.startsWith("https://")) && method !== "CONNECT") { | |
| 2390 | 2405 | throw new InvalidArgumentError("path must be an absolute URL or start with a slash"); | |
| 2406 | + } else if (invalidPathRegex.exec(path) !== null) { | ||
| 2407 | + throw new InvalidArgumentError("invalid request path"); | ||
| 2391 | 2408 | } | |
| 2392 | 2409 | if (typeof method !== "string") { | |
| 2393 | 2410 | throw new InvalidArgumentError("method must be a string"); | |
| 2411 | + } else if (tokenRegExp.exec(method) === null) { | ||
| 2412 | + throw new InvalidArgumentError("invalid request method"); | ||
| 2394 | 2413 | } | |
| 2395 | 2414 | if (upgrade && typeof upgrade !== "string") { | |
| 2396 | 2415 | throw new InvalidArgumentError("upgrade must be a string"); | |
@@ -2562,6 +2581,10 @@ var require_request = __commonJS({ | |||
| 2562 | 2581 | throw new InvalidArgumentError("invalid upgrade header"); | |
| 2563 | 2582 | } else if (key.length === 6 && key.toLowerCase() === "expect") { | |
| 2564 | 2583 | throw new NotSupportedError("expect header not supported"); | |
| 2584 | + } else if (tokenRegExp.exec(key) === null) { | ||
| 2585 | + throw new InvalidArgumentError("invalid header key"); | ||
| 2586 | + } else if (headerCharRegex.exec(val) !== null) { | ||
| 2587 | + throw new InvalidArgumentError(`invalid ${key} header`); | ||
| 2565 | 2588 | } else { | |
| 2566 | 2589 | request.headers += `${key}: ${val}\r | |
| 2567 | 2590 | `; | |
@@ -2685,7 +2708,7 @@ var require_redirect = __commonJS({ | |||
| 2685 | 2708 | } | |
| 2686 | 2709 | } | |
| 2687 | 2710 | function shouldRemoveHeader(header, removeContent, unknownOrigin) { | |
| 2688 | - return header.length === 4 && header.toString().toLowerCase() === "host" || removeContent && header.toString().toLowerCase().indexOf("content-") === 0 || unknownOrigin && header.length === 13 && header.toString().toLowerCase() === "authorization"; | ||
| 2711 | + return header.length === 4 && header.toString().toLowerCase() === "host" || removeContent && header.toString().toLowerCase().indexOf("content-") === 0 || unknownOrigin && header.length === 13 && header.toString().toLowerCase() === "authorization" || unknownOrigin && header.length === 6 && header.toString().toLowerCase() === "cookie"; | ||
| 2689 | 2712 | } | |
| 2690 | 2713 | function cleanRequestHeaders(headers, removeContent, unknownOrigin) { | |
| 2691 | 2714 | const ret = []; | |
| Back | FazBrowse Home | New Git URL |
0 commit comments