mirror of
https://github.com/github/codeql-action.git
synced 2025-12-30 11:10:22 +08:00
1115 lines
37 KiB
JavaScript
1115 lines
37 KiB
JavaScript
import {
|
|
setRawRequestBodyStream
|
|
} from "./chunk-TJDMZZXE.mjs";
|
|
import {
|
|
RequestController,
|
|
createServerErrorResponse,
|
|
emitAsync,
|
|
handleRequest,
|
|
isObject,
|
|
isPropertyAccessible
|
|
} from "./chunk-LGXJ3UUF.mjs";
|
|
import {
|
|
FetchResponse,
|
|
INTERNAL_REQUEST_ID_HEADER_NAME,
|
|
Interceptor,
|
|
createRequestId
|
|
} from "./chunk-IHJSPMYM.mjs";
|
|
import {
|
|
setRawRequest
|
|
} from "./chunk-3GJB4JDF.mjs";
|
|
|
|
// src/interceptors/ClientRequest/index.ts
|
|
import http2 from "http";
|
|
import https2 from "https";
|
|
|
|
// src/interceptors/ClientRequest/MockHttpSocket.ts
|
|
import net2 from "net";
|
|
import {
|
|
HTTPParser
|
|
} from "_http_common";
|
|
import { STATUS_CODES, IncomingMessage, ServerResponse } from "http";
|
|
import { Readable } from "stream";
|
|
import { invariant } from "outvariant";
|
|
|
|
// src/interceptors/Socket/MockSocket.ts
|
|
import net from "net";
|
|
|
|
// src/interceptors/Socket/utils/normalizeSocketWriteArgs.ts
|
|
function normalizeSocketWriteArgs(args) {
|
|
const normalized = [args[0], void 0, void 0];
|
|
if (typeof args[1] === "string") {
|
|
normalized[1] = args[1];
|
|
} else if (typeof args[1] === "function") {
|
|
normalized[2] = args[1];
|
|
}
|
|
if (typeof args[2] === "function") {
|
|
normalized[2] = args[2];
|
|
}
|
|
return normalized;
|
|
}
|
|
|
|
// src/interceptors/Socket/MockSocket.ts
|
|
var MockSocket = class extends net.Socket {
|
|
constructor(options) {
|
|
super();
|
|
this.options = options;
|
|
this.connecting = false;
|
|
this.connect();
|
|
this._final = (callback) => {
|
|
callback(null);
|
|
};
|
|
}
|
|
connect() {
|
|
this.connecting = true;
|
|
return this;
|
|
}
|
|
write(...args) {
|
|
const [chunk, encoding, callback] = normalizeSocketWriteArgs(
|
|
args
|
|
);
|
|
this.options.write(chunk, encoding, callback);
|
|
return true;
|
|
}
|
|
end(...args) {
|
|
const [chunk, encoding, callback] = normalizeSocketWriteArgs(
|
|
args
|
|
);
|
|
this.options.write(chunk, encoding, callback);
|
|
return super.end.apply(this, args);
|
|
}
|
|
push(chunk, encoding) {
|
|
this.options.read(chunk, encoding);
|
|
return super.push(chunk, encoding);
|
|
}
|
|
};
|
|
|
|
// src/interceptors/Socket/utils/baseUrlFromConnectionOptions.ts
|
|
function baseUrlFromConnectionOptions(options) {
|
|
if ("href" in options) {
|
|
return new URL(options.href);
|
|
}
|
|
const protocol = options.port === 443 ? "https:" : "http:";
|
|
const host = options.host;
|
|
const url = new URL(`${protocol}//${host}`);
|
|
if (options.port) {
|
|
url.port = options.port.toString();
|
|
}
|
|
if (options.path) {
|
|
url.pathname = options.path;
|
|
}
|
|
if (options.auth) {
|
|
const [username, password] = options.auth.split(":");
|
|
url.username = username;
|
|
url.password = password;
|
|
}
|
|
return url;
|
|
}
|
|
|
|
// src/interceptors/ClientRequest/utils/recordRawHeaders.ts
|
|
var kRawHeaders = Symbol("kRawHeaders");
|
|
var kRestorePatches = Symbol("kRestorePatches");
|
|
function recordRawHeader(headers, args, behavior) {
|
|
ensureRawHeadersSymbol(headers, []);
|
|
const rawHeaders = Reflect.get(headers, kRawHeaders);
|
|
if (behavior === "set") {
|
|
for (let index = rawHeaders.length - 1; index >= 0; index--) {
|
|
if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) {
|
|
rawHeaders.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
rawHeaders.push(args);
|
|
}
|
|
function ensureRawHeadersSymbol(headers, rawHeaders) {
|
|
if (Reflect.has(headers, kRawHeaders)) {
|
|
return;
|
|
}
|
|
defineRawHeadersSymbol(headers, rawHeaders);
|
|
}
|
|
function defineRawHeadersSymbol(headers, rawHeaders) {
|
|
Object.defineProperty(headers, kRawHeaders, {
|
|
value: rawHeaders,
|
|
enumerable: false,
|
|
// Mark the symbol as configurable so its value can be overridden.
|
|
// Overrides happen when merging raw headers from multiple sources.
|
|
// E.g. new Request(new Request(url, { headers }), { headers })
|
|
configurable: true
|
|
});
|
|
}
|
|
function recordRawFetchHeaders() {
|
|
if (Reflect.get(Headers, kRestorePatches)) {
|
|
return Reflect.get(Headers, kRestorePatches);
|
|
}
|
|
const {
|
|
Headers: OriginalHeaders,
|
|
Request: OriginalRequest,
|
|
Response: OriginalResponse
|
|
} = globalThis;
|
|
const { set, append, delete: headersDeleteMethod } = Headers.prototype;
|
|
Object.defineProperty(Headers, kRestorePatches, {
|
|
value: () => {
|
|
Headers.prototype.set = set;
|
|
Headers.prototype.append = append;
|
|
Headers.prototype.delete = headersDeleteMethod;
|
|
globalThis.Headers = OriginalHeaders;
|
|
globalThis.Request = OriginalRequest;
|
|
globalThis.Response = OriginalResponse;
|
|
Reflect.deleteProperty(Headers, kRestorePatches);
|
|
},
|
|
enumerable: false,
|
|
/**
|
|
* @note Mark this property as configurable
|
|
* so we can delete it using `Reflect.delete` during cleanup.
|
|
*/
|
|
configurable: true
|
|
});
|
|
Object.defineProperty(globalThis, "Headers", {
|
|
enumerable: true,
|
|
writable: true,
|
|
value: new Proxy(Headers, {
|
|
construct(target, args, newTarget) {
|
|
const headersInit = args[0] || [];
|
|
if (headersInit instanceof Headers && Reflect.has(headersInit, kRawHeaders)) {
|
|
const headers2 = Reflect.construct(
|
|
target,
|
|
[Reflect.get(headersInit, kRawHeaders)],
|
|
newTarget
|
|
);
|
|
ensureRawHeadersSymbol(headers2, [
|
|
/**
|
|
* @note Spread the retrieved headers to clone them.
|
|
* This prevents multiple Headers instances from pointing
|
|
* at the same internal "rawHeaders" array.
|
|
*/
|
|
...Reflect.get(headersInit, kRawHeaders)
|
|
]);
|
|
return headers2;
|
|
}
|
|
const headers = Reflect.construct(target, args, newTarget);
|
|
if (!Reflect.has(headers, kRawHeaders)) {
|
|
const rawHeadersInit = Array.isArray(headersInit) ? headersInit : Object.entries(headersInit);
|
|
ensureRawHeadersSymbol(headers, rawHeadersInit);
|
|
}
|
|
return headers;
|
|
}
|
|
})
|
|
});
|
|
Headers.prototype.set = new Proxy(Headers.prototype.set, {
|
|
apply(target, thisArg, args) {
|
|
recordRawHeader(thisArg, args, "set");
|
|
return Reflect.apply(target, thisArg, args);
|
|
}
|
|
});
|
|
Headers.prototype.append = new Proxy(Headers.prototype.append, {
|
|
apply(target, thisArg, args) {
|
|
recordRawHeader(thisArg, args, "append");
|
|
return Reflect.apply(target, thisArg, args);
|
|
}
|
|
});
|
|
Headers.prototype.delete = new Proxy(Headers.prototype.delete, {
|
|
apply(target, thisArg, args) {
|
|
const rawHeaders = Reflect.get(thisArg, kRawHeaders);
|
|
if (rawHeaders) {
|
|
for (let index = rawHeaders.length - 1; index >= 0; index--) {
|
|
if (rawHeaders[index][0].toLowerCase() === args[0].toLowerCase()) {
|
|
rawHeaders.splice(index, 1);
|
|
}
|
|
}
|
|
}
|
|
return Reflect.apply(target, thisArg, args);
|
|
}
|
|
});
|
|
Object.defineProperty(globalThis, "Request", {
|
|
enumerable: true,
|
|
writable: true,
|
|
value: new Proxy(Request, {
|
|
construct(target, args, newTarget) {
|
|
const request = Reflect.construct(target, args, newTarget);
|
|
const inferredRawHeaders = [];
|
|
if (typeof args[0] === "object" && args[0].headers != null) {
|
|
inferredRawHeaders.push(...inferRawHeaders(args[0].headers));
|
|
}
|
|
if (typeof args[1] === "object" && args[1].headers != null) {
|
|
inferredRawHeaders.push(...inferRawHeaders(args[1].headers));
|
|
}
|
|
if (inferredRawHeaders.length > 0) {
|
|
ensureRawHeadersSymbol(request.headers, inferredRawHeaders);
|
|
}
|
|
return request;
|
|
}
|
|
})
|
|
});
|
|
Object.defineProperty(globalThis, "Response", {
|
|
enumerable: true,
|
|
writable: true,
|
|
value: new Proxy(Response, {
|
|
construct(target, args, newTarget) {
|
|
const response = Reflect.construct(target, args, newTarget);
|
|
if (typeof args[1] === "object" && args[1].headers != null) {
|
|
ensureRawHeadersSymbol(
|
|
response.headers,
|
|
inferRawHeaders(args[1].headers)
|
|
);
|
|
}
|
|
return response;
|
|
}
|
|
})
|
|
});
|
|
}
|
|
function restoreHeadersPrototype() {
|
|
if (!Reflect.get(Headers, kRestorePatches)) {
|
|
return;
|
|
}
|
|
Reflect.get(Headers, kRestorePatches)();
|
|
}
|
|
function getRawFetchHeaders(headers) {
|
|
if (!Reflect.has(headers, kRawHeaders)) {
|
|
return Array.from(headers.entries());
|
|
}
|
|
const rawHeaders = Reflect.get(headers, kRawHeaders);
|
|
return rawHeaders.length > 0 ? rawHeaders : Array.from(headers.entries());
|
|
}
|
|
function inferRawHeaders(headers) {
|
|
if (headers instanceof Headers) {
|
|
return Reflect.get(headers, kRawHeaders) || [];
|
|
}
|
|
return Reflect.get(new Headers(headers), kRawHeaders);
|
|
}
|
|
|
|
// src/interceptors/ClientRequest/MockHttpSocket.ts
|
|
var kRequestId = Symbol("kRequestId");
|
|
var MockHttpSocket = class extends MockSocket {
|
|
constructor(options) {
|
|
super({
|
|
write: (chunk, encoding, callback) => {
|
|
var _a;
|
|
if (this.socketState !== "passthrough") {
|
|
this.writeBuffer.push([chunk, encoding, callback]);
|
|
}
|
|
if (chunk) {
|
|
if (this.socketState === "passthrough") {
|
|
(_a = this.originalSocket) == null ? void 0 : _a.write(chunk, encoding, callback);
|
|
}
|
|
this.requestParser.execute(
|
|
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, encoding)
|
|
);
|
|
}
|
|
},
|
|
read: (chunk) => {
|
|
if (chunk !== null) {
|
|
this.responseParser.execute(
|
|
Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
|
|
);
|
|
}
|
|
}
|
|
});
|
|
this.requestRawHeadersBuffer = [];
|
|
this.responseRawHeadersBuffer = [];
|
|
this.writeBuffer = [];
|
|
this.socketState = "unknown";
|
|
/**
|
|
* This callback might be called when the request is "slow":
|
|
* - Request headers were fragmented across multiple TCP packages;
|
|
* - Request headers were too large to be processed in a single run
|
|
* (e.g. more than 30 request headers).
|
|
* @note This is called before request start.
|
|
*/
|
|
this.onRequestHeaders = (rawHeaders) => {
|
|
this.requestRawHeadersBuffer.push(...rawHeaders);
|
|
};
|
|
this.onRequestStart = (versionMajor, versionMinor, rawHeaders, _, path, __, ___, ____, shouldKeepAlive) => {
|
|
var _a;
|
|
this.shouldKeepAlive = shouldKeepAlive;
|
|
const url = new URL(path || "", this.baseUrl);
|
|
const method = ((_a = this.connectionOptions.method) == null ? void 0 : _a.toUpperCase()) || "GET";
|
|
const headers = FetchResponse.parseRawHeaders([
|
|
...this.requestRawHeadersBuffer,
|
|
...rawHeaders || []
|
|
]);
|
|
this.requestRawHeadersBuffer.length = 0;
|
|
const canHaveBody = method !== "GET" && method !== "HEAD";
|
|
if (url.username || url.password) {
|
|
if (!headers.has("authorization")) {
|
|
headers.set("authorization", `Basic ${url.username}:${url.password}`);
|
|
}
|
|
url.username = "";
|
|
url.password = "";
|
|
}
|
|
this.requestStream = new Readable({
|
|
/**
|
|
* @note Provide the `read()` method so a `Readable` could be
|
|
* used as the actual request body (the stream calls "read()").
|
|
* We control the queue in the onRequestBody/End functions.
|
|
*/
|
|
read: () => {
|
|
this.flushWriteBuffer();
|
|
}
|
|
});
|
|
const requestId = createRequestId();
|
|
this.request = new Request(url, {
|
|
method,
|
|
headers,
|
|
credentials: "same-origin",
|
|
// @ts-expect-error Undocumented Fetch property.
|
|
duplex: canHaveBody ? "half" : void 0,
|
|
body: canHaveBody ? Readable.toWeb(this.requestStream) : null
|
|
});
|
|
Reflect.set(this.request, kRequestId, requestId);
|
|
setRawRequest(this.request, Reflect.get(this, "_httpMessage"));
|
|
setRawRequestBodyStream(this.request, this.requestStream);
|
|
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
|
|
this.passthrough();
|
|
return;
|
|
}
|
|
this.onRequest({
|
|
requestId,
|
|
request: this.request,
|
|
socket: this
|
|
});
|
|
};
|
|
/**
|
|
* This callback might be called when the response is "slow":
|
|
* - Response headers were fragmented across multiple TCP packages;
|
|
* - Response headers were too large to be processed in a single run
|
|
* (e.g. more than 30 response headers).
|
|
* @note This is called before response start.
|
|
*/
|
|
this.onResponseHeaders = (rawHeaders) => {
|
|
this.responseRawHeadersBuffer.push(...rawHeaders);
|
|
};
|
|
this.onResponseStart = (versionMajor, versionMinor, rawHeaders, method, url, status, statusText) => {
|
|
const headers = FetchResponse.parseRawHeaders([
|
|
...this.responseRawHeadersBuffer,
|
|
...rawHeaders || []
|
|
]);
|
|
this.responseRawHeadersBuffer.length = 0;
|
|
const response = new FetchResponse(
|
|
/**
|
|
* @note The Fetch API response instance exposed to the consumer
|
|
* is created over the response stream of the HTTP parser. It is NOT
|
|
* related to the Socket instance. This way, you can read response body
|
|
* in response listener while the Socket instance delays the emission
|
|
* of "end" and other events until those response listeners are finished.
|
|
*/
|
|
FetchResponse.isResponseWithBody(status) ? Readable.toWeb(
|
|
this.responseStream = new Readable({ read() {
|
|
} })
|
|
) : null,
|
|
{
|
|
url,
|
|
status,
|
|
statusText,
|
|
headers
|
|
}
|
|
);
|
|
invariant(
|
|
this.request,
|
|
"Failed to handle a response: request does not exist"
|
|
);
|
|
FetchResponse.setUrl(this.request.url, response);
|
|
if (this.request.headers.has(INTERNAL_REQUEST_ID_HEADER_NAME)) {
|
|
return;
|
|
}
|
|
this.responseListenersPromise = this.onResponse({
|
|
response,
|
|
isMockedResponse: this.socketState === "mock",
|
|
requestId: Reflect.get(this.request, kRequestId),
|
|
request: this.request,
|
|
socket: this
|
|
});
|
|
};
|
|
this.connectionOptions = options.connectionOptions;
|
|
this.createConnection = options.createConnection;
|
|
this.onRequest = options.onRequest;
|
|
this.onResponse = options.onResponse;
|
|
this.baseUrl = baseUrlFromConnectionOptions(this.connectionOptions);
|
|
this.requestParser = new HTTPParser();
|
|
this.requestParser.initialize(HTTPParser.REQUEST, {});
|
|
this.requestParser[HTTPParser.kOnHeaders] = this.onRequestHeaders.bind(this);
|
|
this.requestParser[HTTPParser.kOnHeadersComplete] = this.onRequestStart.bind(this);
|
|
this.requestParser[HTTPParser.kOnBody] = this.onRequestBody.bind(this);
|
|
this.requestParser[HTTPParser.kOnMessageComplete] = this.onRequestEnd.bind(this);
|
|
this.responseParser = new HTTPParser();
|
|
this.responseParser.initialize(HTTPParser.RESPONSE, {});
|
|
this.responseParser[HTTPParser.kOnHeaders] = this.onResponseHeaders.bind(this);
|
|
this.responseParser[HTTPParser.kOnHeadersComplete] = this.onResponseStart.bind(this);
|
|
this.responseParser[HTTPParser.kOnBody] = this.onResponseBody.bind(this);
|
|
this.responseParser[HTTPParser.kOnMessageComplete] = this.onResponseEnd.bind(this);
|
|
this.once("finish", () => this.requestParser.free());
|
|
if (this.baseUrl.protocol === "https:") {
|
|
Reflect.set(this, "encrypted", true);
|
|
Reflect.set(this, "authorized", false);
|
|
Reflect.set(this, "getProtocol", () => "TLSv1.3");
|
|
Reflect.set(this, "getSession", () => void 0);
|
|
Reflect.set(this, "isSessionReused", () => false);
|
|
}
|
|
}
|
|
emit(event, ...args) {
|
|
const emitEvent = super.emit.bind(this, event, ...args);
|
|
if (this.responseListenersPromise) {
|
|
this.responseListenersPromise.finally(emitEvent);
|
|
return this.listenerCount(event) > 0;
|
|
}
|
|
return emitEvent();
|
|
}
|
|
destroy(error) {
|
|
this.responseParser.free();
|
|
if (error) {
|
|
this.emit("error", error);
|
|
}
|
|
return super.destroy(error);
|
|
}
|
|
/**
|
|
* Establish this Socket connection as-is and pipe
|
|
* its data/events through this Socket.
|
|
*/
|
|
passthrough() {
|
|
this.socketState = "passthrough";
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
const socket = this.createConnection();
|
|
this.originalSocket = socket;
|
|
if ("_handle" in socket) {
|
|
Object.defineProperty(this, "_handle", {
|
|
value: socket._handle,
|
|
enumerable: true,
|
|
writable: true
|
|
});
|
|
}
|
|
this.once("error", (error) => {
|
|
socket.destroy(error);
|
|
});
|
|
this.address = socket.address.bind(socket);
|
|
let writeArgs;
|
|
let headersWritten = false;
|
|
while (writeArgs = this.writeBuffer.shift()) {
|
|
if (writeArgs !== void 0) {
|
|
if (!headersWritten) {
|
|
const [chunk, encoding, callback] = writeArgs;
|
|
const chunkString = chunk.toString();
|
|
const chunkBeforeRequestHeaders = chunkString.slice(
|
|
0,
|
|
chunkString.indexOf("\r\n") + 2
|
|
);
|
|
const chunkAfterRequestHeaders = chunkString.slice(
|
|
chunk.indexOf("\r\n\r\n")
|
|
);
|
|
const rawRequestHeaders = getRawFetchHeaders(this.request.headers);
|
|
const requestHeadersString = rawRequestHeaders.filter(([name]) => {
|
|
return name.toLowerCase() !== INTERNAL_REQUEST_ID_HEADER_NAME;
|
|
}).map(([name, value]) => `${name}: ${value}`).join("\r\n");
|
|
const headersChunk = `${chunkBeforeRequestHeaders}${requestHeadersString}${chunkAfterRequestHeaders}`;
|
|
socket.write(headersChunk, encoding, callback);
|
|
headersWritten = true;
|
|
continue;
|
|
}
|
|
socket.write(...writeArgs);
|
|
}
|
|
}
|
|
if (Reflect.get(socket, "encrypted")) {
|
|
const tlsProperties = [
|
|
"encrypted",
|
|
"authorized",
|
|
"getProtocol",
|
|
"getSession",
|
|
"isSessionReused"
|
|
];
|
|
tlsProperties.forEach((propertyName) => {
|
|
Object.defineProperty(this, propertyName, {
|
|
enumerable: true,
|
|
get: () => {
|
|
const value = Reflect.get(socket, propertyName);
|
|
return typeof value === "function" ? value.bind(socket) : value;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
socket.on("lookup", (...args) => this.emit("lookup", ...args)).on("connect", () => {
|
|
this.connecting = socket.connecting;
|
|
this.emit("connect");
|
|
}).on("secureConnect", () => this.emit("secureConnect")).on("secure", () => this.emit("secure")).on("session", (session) => this.emit("session", session)).on("ready", () => this.emit("ready")).on("drain", () => this.emit("drain")).on("data", (chunk) => {
|
|
this.push(chunk);
|
|
}).on("error", (error) => {
|
|
Reflect.set(this, "_hadError", Reflect.get(socket, "_hadError"));
|
|
this.emit("error", error);
|
|
}).on("resume", () => this.emit("resume")).on("timeout", () => this.emit("timeout")).on("prefinish", () => this.emit("prefinish")).on("finish", () => this.emit("finish")).on("close", (hadError) => this.emit("close", hadError)).on("end", () => this.emit("end"));
|
|
}
|
|
/**
|
|
* Convert the given Fetch API `Response` instance to an
|
|
* HTTP message and push it to the socket.
|
|
*/
|
|
async respondWith(response) {
|
|
var _a;
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
if (isPropertyAccessible(response, "type") && response.type === "error") {
|
|
this.errorWith(new TypeError("Network error"));
|
|
return;
|
|
}
|
|
this.mockConnect();
|
|
this.socketState = "mock";
|
|
this.flushWriteBuffer();
|
|
const serverResponse = new ServerResponse(new IncomingMessage(this));
|
|
serverResponse.assignSocket(
|
|
new MockSocket({
|
|
write: (chunk, encoding, callback) => {
|
|
this.push(chunk, encoding);
|
|
callback == null ? void 0 : callback();
|
|
},
|
|
read() {
|
|
}
|
|
})
|
|
);
|
|
serverResponse.removeHeader("connection");
|
|
serverResponse.removeHeader("date");
|
|
const rawResponseHeaders = getRawFetchHeaders(response.headers);
|
|
serverResponse.writeHead(
|
|
response.status,
|
|
response.statusText || STATUS_CODES[response.status],
|
|
rawResponseHeaders
|
|
);
|
|
this.once("error", () => {
|
|
serverResponse.destroy();
|
|
});
|
|
if (response.body) {
|
|
try {
|
|
const reader = response.body.getReader();
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done) {
|
|
serverResponse.end();
|
|
break;
|
|
}
|
|
serverResponse.write(value);
|
|
}
|
|
} catch (error) {
|
|
this.respondWith(createServerErrorResponse(error));
|
|
return;
|
|
}
|
|
} else {
|
|
serverResponse.end();
|
|
}
|
|
if (!this.shouldKeepAlive) {
|
|
this.emit("readable");
|
|
(_a = this.responseStream) == null ? void 0 : _a.push(null);
|
|
this.push(null);
|
|
}
|
|
}
|
|
/**
|
|
* Close this socket connection with the given error.
|
|
*/
|
|
errorWith(error) {
|
|
this.destroy(error);
|
|
}
|
|
mockConnect() {
|
|
this.connecting = false;
|
|
const isIPv6 = net2.isIPv6(this.connectionOptions.hostname) || this.connectionOptions.family === 6;
|
|
const addressInfo = {
|
|
address: isIPv6 ? "::1" : "127.0.0.1",
|
|
family: isIPv6 ? "IPv6" : "IPv4",
|
|
port: this.connectionOptions.port
|
|
};
|
|
this.address = () => addressInfo;
|
|
this.emit(
|
|
"lookup",
|
|
null,
|
|
addressInfo.address,
|
|
addressInfo.family === "IPv6" ? 6 : 4,
|
|
this.connectionOptions.host
|
|
);
|
|
this.emit("connect");
|
|
this.emit("ready");
|
|
if (this.baseUrl.protocol === "https:") {
|
|
this.emit("secure");
|
|
this.emit("secureConnect");
|
|
this.emit(
|
|
"session",
|
|
this.connectionOptions.session || Buffer.from("mock-session-renegotiate")
|
|
);
|
|
this.emit("session", Buffer.from("mock-session-resume"));
|
|
}
|
|
}
|
|
flushWriteBuffer() {
|
|
for (const writeCall of this.writeBuffer) {
|
|
if (typeof writeCall[2] === "function") {
|
|
writeCall[2]();
|
|
writeCall[2] = void 0;
|
|
}
|
|
}
|
|
}
|
|
onRequestBody(chunk) {
|
|
invariant(
|
|
this.requestStream,
|
|
"Failed to write to a request stream: stream does not exist"
|
|
);
|
|
this.requestStream.push(chunk);
|
|
}
|
|
onRequestEnd() {
|
|
if (this.requestStream) {
|
|
this.requestStream.push(null);
|
|
}
|
|
}
|
|
onResponseBody(chunk) {
|
|
invariant(
|
|
this.responseStream,
|
|
"Failed to write to a response stream: stream does not exist"
|
|
);
|
|
this.responseStream.push(chunk);
|
|
}
|
|
onResponseEnd() {
|
|
if (this.responseStream) {
|
|
this.responseStream.push(null);
|
|
}
|
|
}
|
|
};
|
|
|
|
// src/interceptors/ClientRequest/agents.ts
|
|
import http from "http";
|
|
import https from "https";
|
|
var MockAgent = class extends http.Agent {
|
|
constructor(options) {
|
|
super();
|
|
this.customAgent = options.customAgent;
|
|
this.onRequest = options.onRequest;
|
|
this.onResponse = options.onResponse;
|
|
}
|
|
createConnection(options, callback) {
|
|
const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection;
|
|
const createConnectionOptions = this.customAgent instanceof http.Agent ? {
|
|
...options,
|
|
...this.customAgent.options
|
|
} : options;
|
|
const socket = new MockHttpSocket({
|
|
connectionOptions: options,
|
|
createConnection: createConnection.bind(
|
|
this.customAgent || this,
|
|
createConnectionOptions,
|
|
callback
|
|
),
|
|
onRequest: this.onRequest.bind(this),
|
|
onResponse: this.onResponse.bind(this)
|
|
});
|
|
return socket;
|
|
}
|
|
};
|
|
var MockHttpsAgent = class extends https.Agent {
|
|
constructor(options) {
|
|
super();
|
|
this.customAgent = options.customAgent;
|
|
this.onRequest = options.onRequest;
|
|
this.onResponse = options.onResponse;
|
|
}
|
|
createConnection(options, callback) {
|
|
const createConnection = this.customAgent instanceof http.Agent ? this.customAgent.createConnection : super.createConnection;
|
|
const createConnectionOptions = this.customAgent instanceof http.Agent ? {
|
|
...options,
|
|
...this.customAgent.options
|
|
} : options;
|
|
const socket = new MockHttpSocket({
|
|
connectionOptions: options,
|
|
createConnection: createConnection.bind(
|
|
this.customAgent || this,
|
|
createConnectionOptions,
|
|
callback
|
|
),
|
|
onRequest: this.onRequest.bind(this),
|
|
onResponse: this.onResponse.bind(this)
|
|
});
|
|
return socket;
|
|
}
|
|
};
|
|
|
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
import { urlToHttpOptions } from "url";
|
|
import {
|
|
globalAgent as httpGlobalAgent
|
|
} from "http";
|
|
import {
|
|
globalAgent as httpsGlobalAgent
|
|
} from "https";
|
|
import {
|
|
URL as URL2,
|
|
parse as parseUrl
|
|
} from "url";
|
|
import { Logger as Logger3 } from "@open-draft/logger";
|
|
|
|
// src/utils/getUrlByRequestOptions.ts
|
|
import { Agent } from "http";
|
|
import { Logger } from "@open-draft/logger";
|
|
var logger = new Logger("utils getUrlByRequestOptions");
|
|
var DEFAULT_PATH = "/";
|
|
var DEFAULT_PROTOCOL = "http:";
|
|
var DEFAULT_HOSTNAME = "localhost";
|
|
var SSL_PORT = 443;
|
|
function getAgent(options) {
|
|
return options.agent instanceof Agent ? options.agent : void 0;
|
|
}
|
|
function getProtocolByRequestOptions(options) {
|
|
var _a;
|
|
if (options.protocol) {
|
|
return options.protocol;
|
|
}
|
|
const agent = getAgent(options);
|
|
const agentProtocol = agent == null ? void 0 : agent.protocol;
|
|
if (agentProtocol) {
|
|
return agentProtocol;
|
|
}
|
|
const port = getPortByRequestOptions(options);
|
|
const isSecureRequest = options.cert || port === SSL_PORT;
|
|
return isSecureRequest ? "https:" : ((_a = options.uri) == null ? void 0 : _a.protocol) || DEFAULT_PROTOCOL;
|
|
}
|
|
function getPortByRequestOptions(options) {
|
|
if (options.port) {
|
|
return Number(options.port);
|
|
}
|
|
const agent = getAgent(options);
|
|
if (agent == null ? void 0 : agent.options.port) {
|
|
return Number(agent.options.port);
|
|
}
|
|
if (agent == null ? void 0 : agent.defaultPort) {
|
|
return Number(agent.defaultPort);
|
|
}
|
|
return void 0;
|
|
}
|
|
function getAuthByRequestOptions(options) {
|
|
if (options.auth) {
|
|
const [username, password] = options.auth.split(":");
|
|
return { username, password };
|
|
}
|
|
}
|
|
function isRawIPv6Address(host) {
|
|
return host.includes(":") && !host.startsWith("[") && !host.endsWith("]");
|
|
}
|
|
function getHostname(options) {
|
|
let host = options.hostname || options.host;
|
|
if (host) {
|
|
if (isRawIPv6Address(host)) {
|
|
host = `[${host}]`;
|
|
}
|
|
return new URL(`http://${host}`).hostname;
|
|
}
|
|
return DEFAULT_HOSTNAME;
|
|
}
|
|
function getUrlByRequestOptions(options) {
|
|
logger.info("request options", options);
|
|
if (options.uri) {
|
|
logger.info(
|
|
'constructing url from explicitly provided "options.uri": %s',
|
|
options.uri
|
|
);
|
|
return new URL(options.uri.href);
|
|
}
|
|
logger.info("figuring out url from request options...");
|
|
const protocol = getProtocolByRequestOptions(options);
|
|
logger.info("protocol", protocol);
|
|
const port = getPortByRequestOptions(options);
|
|
logger.info("port", port);
|
|
const hostname = getHostname(options);
|
|
logger.info("hostname", hostname);
|
|
const path = options.path || DEFAULT_PATH;
|
|
logger.info("path", path);
|
|
const credentials = getAuthByRequestOptions(options);
|
|
logger.info("credentials", credentials);
|
|
const authString = credentials ? `${credentials.username}:${credentials.password}@` : "";
|
|
logger.info("auth string:", authString);
|
|
const portString = typeof port !== "undefined" ? `:${port}` : "";
|
|
const url = new URL(`${protocol}//${hostname}${portString}${path}`);
|
|
url.username = (credentials == null ? void 0 : credentials.username) || "";
|
|
url.password = (credentials == null ? void 0 : credentials.password) || "";
|
|
logger.info("created url:", url);
|
|
return url;
|
|
}
|
|
|
|
// src/utils/cloneObject.ts
|
|
import { Logger as Logger2 } from "@open-draft/logger";
|
|
var logger2 = new Logger2("cloneObject");
|
|
function isPlainObject(obj) {
|
|
var _a;
|
|
logger2.info("is plain object?", obj);
|
|
if (obj == null || !((_a = obj.constructor) == null ? void 0 : _a.name)) {
|
|
logger2.info("given object is undefined, not a plain object...");
|
|
return false;
|
|
}
|
|
logger2.info("checking the object constructor:", obj.constructor.name);
|
|
return obj.constructor.name === "Object";
|
|
}
|
|
function cloneObject(obj) {
|
|
logger2.info("cloning object:", obj);
|
|
const enumerableProperties = Object.entries(obj).reduce(
|
|
(acc, [key, value]) => {
|
|
logger2.info("analyzing key-value pair:", key, value);
|
|
acc[key] = isPlainObject(value) ? cloneObject(value) : value;
|
|
return acc;
|
|
},
|
|
{}
|
|
);
|
|
return isPlainObject(obj) ? enumerableProperties : Object.assign(Object.getPrototypeOf(obj), enumerableProperties);
|
|
}
|
|
|
|
// src/interceptors/ClientRequest/utils/normalizeClientRequestArgs.ts
|
|
var logger3 = new Logger3("http normalizeClientRequestArgs");
|
|
function resolveRequestOptions(args, url) {
|
|
if (typeof args[1] === "undefined" || typeof args[1] === "function") {
|
|
logger3.info("request options not provided, deriving from the url", url);
|
|
return urlToHttpOptions(url);
|
|
}
|
|
if (args[1]) {
|
|
logger3.info("has custom RequestOptions!", args[1]);
|
|
const requestOptionsFromUrl = urlToHttpOptions(url);
|
|
logger3.info("derived RequestOptions from the URL:", requestOptionsFromUrl);
|
|
logger3.info("cloning RequestOptions...");
|
|
const clonedRequestOptions = cloneObject(args[1]);
|
|
logger3.info("successfully cloned RequestOptions!", clonedRequestOptions);
|
|
return {
|
|
...requestOptionsFromUrl,
|
|
...clonedRequestOptions
|
|
};
|
|
}
|
|
logger3.info("using an empty object as request options");
|
|
return {};
|
|
}
|
|
function overrideUrlByRequestOptions(url, options) {
|
|
url.host = options.host || url.host;
|
|
url.hostname = options.hostname || url.hostname;
|
|
url.port = options.port ? options.port.toString() : url.port;
|
|
if (options.path) {
|
|
const parsedOptionsPath = parseUrl(options.path, false);
|
|
url.pathname = parsedOptionsPath.pathname || "";
|
|
url.search = parsedOptionsPath.search || "";
|
|
}
|
|
return url;
|
|
}
|
|
function resolveCallback(args) {
|
|
return typeof args[1] === "function" ? args[1] : args[2];
|
|
}
|
|
function normalizeClientRequestArgs(defaultProtocol, args) {
|
|
let url;
|
|
let options;
|
|
let callback;
|
|
logger3.info("arguments", args);
|
|
logger3.info("using default protocol:", defaultProtocol);
|
|
if (args.length === 0) {
|
|
const url2 = new URL2("http://localhost");
|
|
const options2 = resolveRequestOptions(args, url2);
|
|
return [url2, options2];
|
|
}
|
|
if (typeof args[0] === "string") {
|
|
logger3.info("first argument is a location string:", args[0]);
|
|
url = new URL2(args[0]);
|
|
logger3.info("created a url:", url);
|
|
const requestOptionsFromUrl = urlToHttpOptions(url);
|
|
logger3.info("request options from url:", requestOptionsFromUrl);
|
|
options = resolveRequestOptions(args, url);
|
|
logger3.info("resolved request options:", options);
|
|
callback = resolveCallback(args);
|
|
} else if (args[0] instanceof URL2) {
|
|
url = args[0];
|
|
logger3.info("first argument is a URL:", url);
|
|
if (typeof args[1] !== "undefined" && isObject(args[1])) {
|
|
url = overrideUrlByRequestOptions(url, args[1]);
|
|
}
|
|
options = resolveRequestOptions(args, url);
|
|
logger3.info("derived request options:", options);
|
|
callback = resolveCallback(args);
|
|
} else if ("hash" in args[0] && !("method" in args[0])) {
|
|
const [legacyUrl] = args;
|
|
logger3.info("first argument is a legacy URL:", legacyUrl);
|
|
if (legacyUrl.hostname === null) {
|
|
logger3.info("given legacy URL is relative (no hostname)");
|
|
return isObject(args[1]) ? normalizeClientRequestArgs(defaultProtocol, [
|
|
{ path: legacyUrl.path, ...args[1] },
|
|
args[2]
|
|
]) : normalizeClientRequestArgs(defaultProtocol, [
|
|
{ path: legacyUrl.path },
|
|
args[1]
|
|
]);
|
|
}
|
|
logger3.info("given legacy url is absolute");
|
|
const resolvedUrl = new URL2(legacyUrl.href);
|
|
return args[1] === void 0 ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl]) : typeof args[1] === "function" ? normalizeClientRequestArgs(defaultProtocol, [resolvedUrl, args[1]]) : normalizeClientRequestArgs(defaultProtocol, [
|
|
resolvedUrl,
|
|
args[1],
|
|
args[2]
|
|
]);
|
|
} else if (isObject(args[0])) {
|
|
options = { ...args[0] };
|
|
logger3.info("first argument is RequestOptions:", options);
|
|
options.protocol = options.protocol || defaultProtocol;
|
|
logger3.info("normalized request options:", options);
|
|
url = getUrlByRequestOptions(options);
|
|
logger3.info("created a URL from RequestOptions:", url.href);
|
|
callback = resolveCallback(args);
|
|
} else {
|
|
throw new Error(
|
|
`Failed to construct ClientRequest with these parameters: ${args}`
|
|
);
|
|
}
|
|
options.protocol = options.protocol || url.protocol;
|
|
options.method = options.method || "GET";
|
|
if (!options._defaultAgent) {
|
|
logger3.info(
|
|
'has no default agent, setting the default agent for "%s"',
|
|
options.protocol
|
|
);
|
|
options._defaultAgent = options.protocol === "https:" ? httpsGlobalAgent : httpGlobalAgent;
|
|
}
|
|
logger3.info("successfully resolved url:", url.href);
|
|
logger3.info("successfully resolved options:", options);
|
|
logger3.info("successfully resolved callback:", callback);
|
|
if (!(url instanceof URL2)) {
|
|
url = url.toString();
|
|
}
|
|
return [url, options, callback];
|
|
}
|
|
|
|
// src/interceptors/ClientRequest/index.ts
|
|
var _ClientRequestInterceptor = class extends Interceptor {
|
|
constructor() {
|
|
super(_ClientRequestInterceptor.symbol);
|
|
this.onRequest = async ({
|
|
request,
|
|
socket
|
|
}) => {
|
|
const requestId = Reflect.get(request, kRequestId);
|
|
const controller = new RequestController(request);
|
|
const isRequestHandled = await handleRequest({
|
|
request,
|
|
requestId,
|
|
controller,
|
|
emitter: this.emitter,
|
|
onResponse: (response) => {
|
|
socket.respondWith(response);
|
|
},
|
|
onRequestError: (response) => {
|
|
socket.respondWith(response);
|
|
},
|
|
onError: (error) => {
|
|
if (error instanceof Error) {
|
|
socket.errorWith(error);
|
|
}
|
|
}
|
|
});
|
|
if (!isRequestHandled) {
|
|
return socket.passthrough();
|
|
}
|
|
};
|
|
this.onResponse = async ({
|
|
requestId,
|
|
request,
|
|
response,
|
|
isMockedResponse
|
|
}) => {
|
|
return emitAsync(this.emitter, "response", {
|
|
requestId,
|
|
request,
|
|
response,
|
|
isMockedResponse
|
|
});
|
|
};
|
|
}
|
|
setup() {
|
|
const {
|
|
ClientRequest: OriginalClientRequest,
|
|
get: originalGet,
|
|
request: originalRequest
|
|
} = http2;
|
|
const { get: originalHttpsGet, request: originalHttpsRequest } = https2;
|
|
const onRequest = this.onRequest.bind(this);
|
|
const onResponse = this.onResponse.bind(this);
|
|
http2.ClientRequest = new Proxy(http2.ClientRequest, {
|
|
construct: (target, args) => {
|
|
const [url, options, callback] = normalizeClientRequestArgs(
|
|
"http:",
|
|
args
|
|
);
|
|
const Agent2 = options.protocol === "https:" ? MockHttpsAgent : MockAgent;
|
|
const mockAgent = new Agent2({
|
|
customAgent: options.agent,
|
|
onRequest,
|
|
onResponse
|
|
});
|
|
options.agent = mockAgent;
|
|
return Reflect.construct(target, [url, options, callback]);
|
|
}
|
|
});
|
|
http2.request = new Proxy(http2.request, {
|
|
apply: (target, thisArg, args) => {
|
|
const [url, options, callback] = normalizeClientRequestArgs(
|
|
"http:",
|
|
args
|
|
);
|
|
const mockAgent = new MockAgent({
|
|
customAgent: options.agent,
|
|
onRequest,
|
|
onResponse
|
|
});
|
|
options.agent = mockAgent;
|
|
return Reflect.apply(target, thisArg, [url, options, callback]);
|
|
}
|
|
});
|
|
http2.get = new Proxy(http2.get, {
|
|
apply: (target, thisArg, args) => {
|
|
const [url, options, callback] = normalizeClientRequestArgs(
|
|
"http:",
|
|
args
|
|
);
|
|
const mockAgent = new MockAgent({
|
|
customAgent: options.agent,
|
|
onRequest,
|
|
onResponse
|
|
});
|
|
options.agent = mockAgent;
|
|
return Reflect.apply(target, thisArg, [url, options, callback]);
|
|
}
|
|
});
|
|
https2.request = new Proxy(https2.request, {
|
|
apply: (target, thisArg, args) => {
|
|
const [url, options, callback] = normalizeClientRequestArgs(
|
|
"https:",
|
|
args
|
|
);
|
|
const mockAgent = new MockHttpsAgent({
|
|
customAgent: options.agent,
|
|
onRequest,
|
|
onResponse
|
|
});
|
|
options.agent = mockAgent;
|
|
return Reflect.apply(target, thisArg, [url, options, callback]);
|
|
}
|
|
});
|
|
https2.get = new Proxy(https2.get, {
|
|
apply: (target, thisArg, args) => {
|
|
const [url, options, callback] = normalizeClientRequestArgs(
|
|
"https:",
|
|
args
|
|
);
|
|
const mockAgent = new MockHttpsAgent({
|
|
customAgent: options.agent,
|
|
onRequest,
|
|
onResponse
|
|
});
|
|
options.agent = mockAgent;
|
|
return Reflect.apply(target, thisArg, [url, options, callback]);
|
|
}
|
|
});
|
|
recordRawFetchHeaders();
|
|
this.subscriptions.push(() => {
|
|
http2.ClientRequest = OriginalClientRequest;
|
|
http2.get = originalGet;
|
|
http2.request = originalRequest;
|
|
https2.get = originalHttpsGet;
|
|
https2.request = originalHttpsRequest;
|
|
restoreHeadersPrototype();
|
|
});
|
|
}
|
|
};
|
|
var ClientRequestInterceptor = _ClientRequestInterceptor;
|
|
ClientRequestInterceptor.symbol = Symbol("client-request-interceptor");
|
|
|
|
export {
|
|
ClientRequestInterceptor
|
|
};
|
|
//# sourceMappingURL=chunk-WA2XMLKW.mjs.map
|