{"version":3,"file":"parse-response.mjs","names":[],"sources":["../src/parse-response.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 { Buffer } from \"node:buffer\";\nimport type { IncomingHttpHeaders } from \"node:http\";\nimport type { Readable } from \"node:stream\";\n\nexport interface ConnectResponse {\n  statusCode: number;\n  statusText: string;\n  headers: IncomingHttpHeaders;\n}\n\n/**\n * Parses the proxy CONNECT response from the given socket.\n *\n * @param socket - The socket to read the response from.\n * @returns A promise that resolves to the CONNECT response and any buffered data.\n */\nexport async function parseProxyResponse(\n  socket: Readable\n): Promise<{ connect: ConnectResponse; buffered: Buffer }> {\n  return new Promise((resolve, reject) => {\n    // we need to buffer any HTTP traffic that happens with the proxy before we get\n    // the CONNECT response, so that if the response is anything other than an \"200\"\n    // response code, then we can re-play the \"data\" events on the socket once the\n    // HTTP parser is hooked up...\n    let buffersLength = 0;\n    const buffers: Buffer[] = [];\n\n    function read() {\n      const b = socket.read();\n      if (b) ondata(b);\n      else socket.once(\"readable\", read);\n    }\n\n    function cleanup() {\n      socket.removeListener(\"end\", onend);\n      socket.removeListener(\"error\", onerror);\n      socket.removeListener(\"readable\", read);\n    }\n\n    function onend() {\n      cleanup();\n      reject(\n        new Error(\"Proxy connection ended before receiving CONNECT response\")\n      );\n    }\n\n    function onerror(err: Error) {\n      cleanup();\n      reject(err);\n    }\n\n    function ondata(b: Buffer) {\n      buffers.push(b);\n      buffersLength += b.length;\n\n      const buffered = Buffer.concat(buffers, buffersLength);\n      const endOfHeaders = buffered.indexOf(\"\\r\\n\\r\\n\");\n\n      if (endOfHeaders === -1) {\n        // keep buffering\n        read();\n        return;\n      }\n\n      const headerParts = buffered\n        .subarray(0, endOfHeaders)\n        .toString(\"ascii\")\n        .split(\"\\r\\n\");\n      const firstLine = headerParts.shift();\n      if (!firstLine) {\n        socket.destroy();\n        return reject(\n          new Error(\"No header received from proxy CONNECT response\")\n        );\n      }\n      const firstLineParts = firstLine.split(\" \");\n      const statusCode = +firstLineParts[1]!;\n      const statusText = firstLineParts.slice(2).join(\" \");\n      const headers: IncomingHttpHeaders = {};\n      for (const header of headerParts) {\n        if (!header) continue;\n        const firstColon = header.indexOf(\":\");\n        if (firstColon === -1) {\n          socket.destroy();\n          return reject(\n            new Error(`Invalid header from proxy CONNECT response: \"${header}\"`)\n          );\n        }\n        const key = header.slice(0, firstColon).toLowerCase();\n        const value = header.slice(firstColon + 1).trimStart();\n        const current = headers[key];\n        if (typeof current === \"string\") {\n          headers[key] = [current, value];\n        } else if (Array.isArray(current)) {\n          current.push(value);\n        } else {\n          headers[key] = value;\n        }\n      }\n\n      cleanup();\n      resolve({\n        connect: {\n          statusCode,\n          statusText,\n          headers\n        },\n        buffered\n      });\n    }\n\n    socket.on(\"error\", onerror);\n    socket.on(\"end\", onend);\n\n    read();\n  });\n}\n"],"mappings":";;;;;;;;;AAkCA,eAAsB,mBACpB,QACyD;AACzD,QAAO,IAAI,SAAS,SAAS,WAAW;EAKtC,IAAI,gBAAgB;EACpB,MAAM,UAAoB,EAAE;EAE5B,SAAS,OAAO;GACd,MAAM,IAAI,OAAO,MAAM;AACvB,OAAI,EAAG,QAAO,EAAE;OACX,QAAO,KAAK,YAAY,KAAK;;EAGpC,SAAS,UAAU;AACjB,UAAO,eAAe,OAAO,MAAM;AACnC,UAAO,eAAe,SAAS,QAAQ;AACvC,UAAO,eAAe,YAAY,KAAK;;EAGzC,SAAS,QAAQ;AACf,YAAS;AACT,0BACE,IAAI,MAAM,2DAA2D,CACtE;;EAGH,SAAS,QAAQ,KAAY;AAC3B,YAAS;AACT,UAAO,IAAI;;EAGb,SAAS,OAAO,GAAW;AACzB,WAAQ,KAAK,EAAE;AACf,oBAAiB,EAAE;GAEnB,MAAM,WAAW,OAAO,OAAO,SAAS,cAAc;GACtD,MAAM,eAAe,SAAS,QAAQ,WAAW;AAEjD,OAAI,iBAAiB,IAAI;AAEvB,UAAM;AACN;;GAGF,MAAM,cAAc,SACjB,SAAS,GAAG,aAAa,CACzB,SAAS,QAAQ,CACjB,MAAM,OAAO;GAChB,MAAM,YAAY,YAAY,OAAO;AACrC,OAAI,CAAC,WAAW;AACd,WAAO,SAAS;AAChB,WAAO,uBACL,IAAI,MAAM,iDAAiD,CAC5D;;GAEH,MAAM,iBAAiB,UAAU,MAAM,IAAI;GAC3C,MAAM,aAAa,CAAC,eAAe;GACnC,MAAM,aAAa,eAAe,MAAM,EAAE,CAAC,KAAK,IAAI;GACpD,MAAM,UAA+B,EAAE;AACvC,QAAK,MAAM,UAAU,aAAa;AAChC,QAAI,CAAC,OAAQ;IACb,MAAM,aAAa,OAAO,QAAQ,IAAI;AACtC,QAAI,eAAe,IAAI;AACrB,YAAO,SAAS;AAChB,YAAO,uBACL,IAAI,MAAM,gDAAgD,OAAO,GAAG,CACrE;;IAEH,MAAM,MAAM,OAAO,MAAM,GAAG,WAAW,CAAC,aAAa;IACrD,MAAM,QAAQ,OAAO,MAAM,aAAa,EAAE,CAAC,WAAW;IACtD,MAAM,UAAU,QAAQ;AACxB,QAAI,OAAO,YAAY,SACrB,SAAQ,OAAO,CAAC,SAAS,MAAM;aACtB,MAAM,QAAQ,QAAQ,CAC/B,SAAQ,KAAK,MAAM;QAEnB,SAAQ,OAAO;;AAInB,YAAS;AACT,WAAQ;IACN,SAAS;KACP;KACA;KACA;KACD;IACD;IACD,CAAC;;AAGJ,SAAO,GAAG,SAAS,QAAQ;AAC3B,SAAO,GAAG,OAAO,MAAM;AAEvB,QAAM;GACN"}