UNPKG

@socketsecurity/lib

Version:

Core utilities and infrastructure for Socket.dev security tools

338 lines (337 loc) 10.4 kB
"use strict"; /* Socket Lib - Built with esbuild */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var http_request_exports = {}; __export(http_request_exports, { httpDownload: () => httpDownload, httpGetJson: () => httpGetJson, httpGetText: () => httpGetText, httpRequest: () => httpRequest }); module.exports = __toCommonJS(http_request_exports); var import_fs = require("fs"); let _http; let _https; // @__NO_SIDE_EFFECTS__ function getHttp() { if (_http === void 0) { _http = require("node:http"); } return _http; } // @__NO_SIDE_EFFECTS__ function getHttps() { if (_https === void 0) { _https = require("node:https"); } return _https; } async function httpRequest(url, options) { const { body, followRedirects = true, headers = {}, maxRedirects = 5, method = "GET", retries = 0, retryDelay = 1e3, timeout = 3e4 } = { __proto__: null, ...options }; let lastError; for (let attempt = 0; attempt <= retries; attempt++) { try { return await httpRequestAttempt(url, { body, followRedirects, headers, maxRedirects, method, timeout }); } catch (e) { lastError = e; if (attempt === retries) { break; } const delayMs = retryDelay * 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, delayMs)); } } throw lastError || new Error("Request failed after retries"); } async function httpRequestAttempt(url, options) { const { body, followRedirects = true, headers = {}, maxRedirects = 5, method = "GET", timeout = 3e4 } = { __proto__: null, ...options }; return await new Promise((resolve, reject) => { const parsedUrl = new URL(url); const isHttps = parsedUrl.protocol === "https:"; const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp(); const requestOptions = { headers: { "User-Agent": "socket-registry/1.0", ...headers }, hostname: parsedUrl.hostname, method, path: parsedUrl.pathname + parsedUrl.search, port: parsedUrl.port, timeout }; const request = httpModule.request( requestOptions, (res) => { if (followRedirects && res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { if (maxRedirects <= 0) { reject( new Error( `Too many redirects (exceeded maximum: ${maxRedirects})` ) ); return; } const redirectUrl = res.headers.location.startsWith("http") ? res.headers.location : new URL(res.headers.location, url).toString(); resolve( httpRequestAttempt(redirectUrl, { body, followRedirects, headers, maxRedirects: maxRedirects - 1, method, timeout }) ); return; } const chunks = []; res.on("data", (chunk) => { chunks.push(chunk); }); res.on("end", () => { const responseBody = Buffer.concat(chunks); const ok = res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 300; const response = { arrayBuffer() { return responseBody.buffer.slice( responseBody.byteOffset, responseBody.byteOffset + responseBody.byteLength ); }, body: responseBody, headers: res.headers, json() { return JSON.parse(responseBody.toString("utf8")); }, ok, status: res.statusCode || 0, statusText: res.statusMessage || "", text() { return responseBody.toString("utf8"); } }; resolve(response); }); } ); request.on("error", (error) => { const code = error.code; let message = `HTTP request failed for ${url}: ${error.message} `; if (code === "ENOTFOUND") { message += "DNS lookup failed. Check the hostname and your network connection."; } else if (code === "ECONNREFUSED") { message += "Connection refused. Verify the server is running and accessible."; } else if (code === "ETIMEDOUT") { message += "Request timed out. Check your network or increase the timeout value."; } else if (code === "ECONNRESET") { message += "Connection reset. The server may have closed the connection unexpectedly."; } else { message += "Check your network connection and verify the URL is correct."; } reject(new Error(message, { cause: error })); }); request.on("timeout", () => { request.destroy(); reject(new Error(`Request timed out after ${timeout}ms`)); }); if (body) { request.write(body); } request.end(); }); } async function httpDownload(url, destPath, options) { const { headers = {}, onProgress, retries = 0, retryDelay = 1e3, timeout = 12e4 } = { __proto__: null, ...options }; let lastError; for (let attempt = 0; attempt <= retries; attempt++) { try { return await httpDownloadAttempt(url, destPath, { headers, onProgress, timeout }); } catch (e) { lastError = e; if (attempt === retries) { break; } const delayMs = retryDelay * 2 ** attempt; await new Promise((resolve) => setTimeout(resolve, delayMs)); } } throw lastError || new Error("Download failed after retries"); } async function httpDownloadAttempt(url, destPath, options) { const { headers = {}, onProgress, timeout = 12e4 } = { __proto__: null, ...options }; return await new Promise((resolve, reject) => { const parsedUrl = new URL(url); const isHttps = parsedUrl.protocol === "https:"; const httpModule = isHttps ? /* @__PURE__ */ getHttps() : /* @__PURE__ */ getHttp(); const requestOptions = { headers: { "User-Agent": "socket-registry/1.0", ...headers }, hostname: parsedUrl.hostname, method: "GET", path: parsedUrl.pathname + parsedUrl.search, port: parsedUrl.port, timeout }; let fileStream; let streamClosed = false; const closeStream = () => { if (!streamClosed && fileStream) { streamClosed = true; fileStream.close(); } }; const request = httpModule.request( requestOptions, (res) => { if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) { closeStream(); reject( new Error( `Download failed: HTTP ${res.statusCode} ${res.statusMessage}` ) ); return; } const totalSize = Number.parseInt( res.headers["content-length"] || "0", 10 ); let downloadedSize = 0; fileStream = (0, import_fs.createWriteStream)(destPath); fileStream.on("error", (error) => { closeStream(); const err = new Error(`Failed to write file: ${error.message}`, { cause: error }); reject(err); }); res.on("data", (chunk) => { downloadedSize += chunk.length; if (onProgress && totalSize > 0) { onProgress(downloadedSize, totalSize); } }); res.on("end", () => { fileStream?.close(() => { streamClosed = true; resolve({ path: destPath, size: downloadedSize }); }); }); res.on("error", (error) => { closeStream(); reject(error); }); res.pipe(fileStream); } ); request.on("error", (error) => { closeStream(); const code = error.code; let message = `HTTP download failed for ${url}: ${error.message} `; if (code === "ENOTFOUND") { message += "DNS lookup failed. Check the hostname and your network connection."; } else if (code === "ECONNREFUSED") { message += "Connection refused. Verify the server is running and accessible."; } else if (code === "ETIMEDOUT") { message += "Request timed out. Check your network or increase the timeout value."; } else if (code === "ECONNRESET") { message += "Connection reset. The server may have closed the connection unexpectedly."; } else { message += "Check your network connection and verify the URL is correct."; } reject(new Error(message, { cause: error })); }); request.on("timeout", () => { request.destroy(); closeStream(); reject(new Error(`Download timed out after ${timeout}ms`)); }); request.end(); }); } async function httpGetJson(url, options) { const response = await httpRequest(url, { ...options, method: "GET" }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } try { return response.json(); } catch (e) { throw new Error("Failed to parse JSON response", { cause: e }); } } async function httpGetText(url, options) { const response = await httpRequest(url, { ...options, method: "GET" }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.text(); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { httpDownload, httpGetJson, httpGetText, httpRequest });