{"version":3,"file":"https-proxy.mjs","names":[],"sources":["../src/https-proxy.ts"],"sourcesContent":["/* -------------------------------------------------------------------\n\n                       ⚡ Storm Software - Stryke\n\n This code was released as part of the Stryke project. Stryke\n is maintained by Storm Software under the Apache-2.0 license, and is\n free for commercial and private use. For more information, please visit\n our licensing page at https://stormsoftware.com/licenses/projects/stryke.\n\n Website:                  https://stormsoftware.com\n Repository:               https://github.com/storm-software/stryke\n Documentation:            https://docs.stormsoftware.com/projects/stryke\n Contact:                  https://stormsoftware.com/contact\n\n SPDX-License-Identifier:  Apache-2.0\n\n ------------------------------------------------------------------- */\n\nimport type { AgentConnectOpts } from \"agent-base\";\nimport { Agent } from \"agent-base\";\nimport assert from \"node:assert\";\nimport { Buffer } from \"node:buffer\";\nimport type * as http from \"node:http\";\nimport type { OutgoingHttpHeaders } from \"node:http\";\nimport * as net from \"node:net\";\nimport * as tls from \"node:tls\";\nimport { URL } from \"node:url\";\nimport { parseProxyResponse } from \"./parse-response\";\n\nconst setServernameFromNonIpHost = <\n  T extends { host?: string; servername?: string }\n>(\n  options: T\n) => {\n  if (\n    options.servername === undefined &&\n    options.host &&\n    !net.isIP(options.host)\n  ) {\n    return {\n      ...options,\n      servername: options.host\n    };\n  }\n  return options;\n};\n\ntype Protocol<T> = T extends `${infer Protocol}:${infer _}` ? Protocol : never;\n\ninterface ConnectOptsMap {\n  http: Omit<net.TcpNetConnectOpts, \"host\" | \"port\">;\n  https: Omit<tls.ConnectionOptions, \"host\" | \"port\">;\n}\n\ntype ConnectOpts<T> = {\n  [P in keyof ConnectOptsMap]: Protocol<T> extends P\n    ? ConnectOptsMap[P]\n    : never;\n}[keyof ConnectOptsMap];\n\nexport type HttpsProxyAgentOptions<T> = ConnectOpts<T> &\n  http.AgentOptions & {\n    headers?: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);\n  };\n\n/**\n * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to\n * the specified \"HTTP(s) proxy server\" in order to proxy HTTPS requests.\n *\n * Outgoing HTTP requests are first tunneled through the proxy server using the\n * `CONNECT` HTTP request method to establish a connection to the proxy server,\n * and then the proxy server connects to the destination target and issues the\n * HTTP request from the proxy server.\n *\n * `https:` requests have their socket connection upgraded to TLS once\n * the connection to the proxy server has been established.\n */\nexport class HttpsProxyAgent<Uri extends string> extends Agent {\n  static protocols = [\"http\", \"https\"] as const;\n\n  readonly proxy: URL;\n\n  proxyHeaders: OutgoingHttpHeaders | (() => OutgoingHttpHeaders);\n\n  connectOpts: net.TcpNetConnectOpts & tls.ConnectionOptions;\n\n  constructor(proxy: Uri | URL, opts?: HttpsProxyAgentOptions<Uri>) {\n    super(opts);\n    this.options = { path: undefined };\n    this.proxy = typeof proxy === \"string\" ? new URL(proxy) : proxy;\n    this.proxyHeaders = opts?.headers ?? {};\n\n    // Trim off the brackets from IPv6 addresses\n    const host = (this.proxy.hostname || this.proxy.host).replace(\n      /^\\[|\\]$/g,\n      \"\"\n    );\n    const port = this.proxy.port\n      ? Number.parseInt(this.proxy.port, 10)\n      : this.proxy.protocol === \"https:\"\n        ? 443\n        : 80;\n    this.connectOpts = {\n      // Attempt to negotiate http/1.1 for proxy servers that support http/2\n      ALPNProtocols: [\"http/1.1\"],\n      ...(opts ? omit(opts, \"headers\") : null),\n      host,\n      port\n    };\n  }\n\n  /**\n   * Called when the node-core HTTP client library is creating a new HTTP request.\n   */\n  async connect(\n    req: http.ClientRequest,\n    opts: AgentConnectOpts\n  ): Promise<net.Socket> {\n    const { proxy } = this;\n\n    if (!opts.host) {\n      throw new TypeError('No \"host\" provided');\n    }\n\n    // Create a socket connection to the proxy server.\n    let socket: net.Socket;\n    if (proxy.protocol === \"https:\") {\n      socket = tls.connect(setServernameFromNonIpHost(this.connectOpts));\n    } else {\n      socket = net.connect(this.connectOpts);\n    }\n\n    const headers: OutgoingHttpHeaders =\n      typeof this.proxyHeaders === \"function\"\n        ? this.proxyHeaders()\n        : { ...this.proxyHeaders };\n    const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host;\n    let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\\r\\n`;\n\n    // Inject the `Proxy-Authorization` header if necessary.\n    if (proxy.username || proxy.password) {\n      const auth = `${decodeURIComponent(\n        proxy.username\n      )}:${decodeURIComponent(proxy.password)}`;\n      headers[\"Proxy-Authorization\"] = `Basic ${Buffer.from(auth).toString(\n        \"base64\"\n      )}`;\n    }\n\n    headers.Host = `${host}:${opts.port}`;\n\n    if (!headers[\"Proxy-Connection\"]) {\n      headers[\"Proxy-Connection\"] = this.keepAlive ? \"Keep-Alive\" : \"close\";\n    }\n    for (const name of Object.keys(headers)) {\n      payload += `${name}: ${headers[name]?.toString()}\\r\\n`;\n    }\n\n    const proxyResponsePromise = parseProxyResponse(socket);\n\n    socket.write(`${payload}\\r\\n`);\n\n    const { connect, buffered } = await proxyResponsePromise;\n    req.emit(\"proxyConnect\", connect);\n    this.emit(\"proxyConnect\", connect, req);\n\n    if (connect.statusCode === 200) {\n      req.once(\"socket\", resume);\n\n      if (opts.secureEndpoint) {\n        // The proxy is connecting to a TLS server, so upgrade\n        // this socket connection to a TLS connection.\n        return tls.connect({\n          ...omit(setServernameFromNonIpHost(opts), \"host\", \"path\", \"port\"),\n          socket\n        });\n      }\n\n      return socket;\n    }\n\n    // Some other status code that's not 200... need to re-play the HTTP\n    // header \"data\" events onto the socket once the HTTP machinery is\n    // attached so that the node core `http` can parse and handle the\n    // error status code.\n\n    // Close the original socket, and a new \"fake\" socket is returned\n    // instead, so that the proxy doesn't get the HTTP request\n    // written to it (which may contain `Authorization` headers or other\n    // sensitive data).\n    //\n    // See: https://hackerone.com/reports/541502\n    socket.destroy();\n\n    const fakeSocket = new net.Socket({ writable: false });\n    fakeSocket.readable = true;\n\n    // Need to wait for the \"socket\" event to re-play the \"data\" events.\n    req.once(\"socket\", (s: net.Socket) => {\n      assert(s.listenerCount(\"data\") > 0);\n\n      // Replay the \"buffered\" Buffer onto the fake `socket`, since at\n      // this point the HTTP module machinery has been hooked up for\n      // the user.\n      s.push(buffered);\n      s.push(null);\n    });\n\n    return fakeSocket;\n  }\n}\n\nfunction resume(socket: net.Socket | tls.TLSSocket): void {\n  socket.resume();\n}\n\nfunction omit<T extends object, K extends [...(keyof T)[]]>(\n  obj: T,\n  ...keys: K\n): {\n  [K2 in Exclude<keyof T, K[number]>]: T[K2];\n} {\n  const ret = {} as {\n    [K in keyof typeof obj]: (typeof obj)[K];\n  };\n  let key: keyof typeof obj;\n  for (key in obj) {\n    if (!keys.includes(key)) {\n      ret[key] = obj[key];\n    }\n  }\n  return ret;\n}\n"],"mappings":"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"}