harBackend.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. "use strict";
  2. var __create = Object.create;
  3. var __defProp = Object.defineProperty;
  4. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  5. var __getOwnPropNames = Object.getOwnPropertyNames;
  6. var __getProtoOf = Object.getPrototypeOf;
  7. var __hasOwnProp = Object.prototype.hasOwnProperty;
  8. var __export = (target, all) => {
  9. for (var name in all)
  10. __defProp(target, name, { get: all[name], enumerable: true });
  11. };
  12. var __copyProps = (to, from, except, desc) => {
  13. if (from && typeof from === "object" || typeof from === "function") {
  14. for (let key of __getOwnPropNames(from))
  15. if (!__hasOwnProp.call(to, key) && key !== except)
  16. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  17. }
  18. return to;
  19. };
  20. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  21. // If the importer is in node compatibility mode or this is not an ESM
  22. // file that has been converted to a CommonJS file using a Babel-
  23. // compatible transform (i.e. "__esModule" has not been set), then set
  24. // "default" to the CommonJS "module.exports" for node compatibility.
  25. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  26. mod
  27. ));
  28. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  29. var harBackend_exports = {};
  30. __export(harBackend_exports, {
  31. HarBackend: () => HarBackend
  32. });
  33. module.exports = __toCommonJS(harBackend_exports);
  34. var import_fs = __toESM(require("fs"));
  35. var import_path = __toESM(require("path"));
  36. var import_crypto = require("./utils/crypto");
  37. const redirectStatus = [301, 302, 303, 307, 308];
  38. class HarBackend {
  39. constructor(harFile, baseDir, zipFile) {
  40. this.id = (0, import_crypto.createGuid)();
  41. this._harFile = harFile;
  42. this._baseDir = baseDir;
  43. this._zipFile = zipFile;
  44. }
  45. async lookup(url, method, headers, postData, isNavigationRequest) {
  46. let entry;
  47. try {
  48. entry = await this._harFindResponse(url, method, headers, postData);
  49. } catch (e) {
  50. return { action: "error", message: "HAR error: " + e.message };
  51. }
  52. if (!entry)
  53. return { action: "noentry" };
  54. if (entry.request.url !== url && isNavigationRequest)
  55. return { action: "redirect", redirectURL: entry.request.url };
  56. const response = entry.response;
  57. try {
  58. const buffer = await this._loadContent(response.content);
  59. return {
  60. action: "fulfill",
  61. status: response.status,
  62. headers: response.headers,
  63. body: buffer
  64. };
  65. } catch (e) {
  66. return { action: "error", message: e.message };
  67. }
  68. }
  69. async _loadContent(content) {
  70. const file = content._file;
  71. let buffer;
  72. if (file) {
  73. if (this._zipFile)
  74. buffer = await this._zipFile.read(file);
  75. else
  76. buffer = await import_fs.default.promises.readFile(import_path.default.resolve(this._baseDir, file));
  77. } else {
  78. buffer = Buffer.from(content.text || "", content.encoding === "base64" ? "base64" : "utf-8");
  79. }
  80. return buffer;
  81. }
  82. async _harFindResponse(url, method, headers, postData) {
  83. const harLog = this._harFile.log;
  84. const visited = /* @__PURE__ */ new Set();
  85. while (true) {
  86. const entries = [];
  87. for (const candidate of harLog.entries) {
  88. if (candidate.request.url !== url || candidate.request.method !== method)
  89. continue;
  90. if (method === "POST" && postData && candidate.request.postData) {
  91. const buffer = await this._loadContent(candidate.request.postData);
  92. if (!buffer.equals(postData)) {
  93. const boundary = multipartBoundary(headers);
  94. if (!boundary)
  95. continue;
  96. const candidataBoundary = multipartBoundary(candidate.request.headers);
  97. if (!candidataBoundary)
  98. continue;
  99. if (postData.toString().replaceAll(boundary, "") !== buffer.toString().replaceAll(candidataBoundary, ""))
  100. continue;
  101. }
  102. }
  103. entries.push(candidate);
  104. }
  105. if (!entries.length)
  106. return;
  107. let entry = entries[0];
  108. if (entries.length > 1) {
  109. const list = [];
  110. for (const candidate of entries) {
  111. const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers);
  112. list.push({ candidate, matchingHeaders });
  113. }
  114. list.sort((a, b) => b.matchingHeaders - a.matchingHeaders);
  115. entry = list[0].candidate;
  116. }
  117. if (visited.has(entry))
  118. throw new Error(`Found redirect cycle for ${url}`);
  119. visited.add(entry);
  120. const locationHeader = entry.response.headers.find((h) => h.name.toLowerCase() === "location");
  121. if (redirectStatus.includes(entry.response.status) && locationHeader) {
  122. const locationURL = new URL(locationHeader.value, url);
  123. url = locationURL.toString();
  124. if ((entry.response.status === 301 || entry.response.status === 302) && method === "POST" || entry.response.status === 303 && !["GET", "HEAD"].includes(method)) {
  125. method = "GET";
  126. }
  127. continue;
  128. }
  129. return entry;
  130. }
  131. }
  132. dispose() {
  133. this._zipFile?.close();
  134. }
  135. }
  136. function countMatchingHeaders(harHeaders, headers) {
  137. const set = new Set(headers.map((h) => h.name.toLowerCase() + ":" + h.value));
  138. let matches = 0;
  139. for (const h of harHeaders) {
  140. if (set.has(h.name.toLowerCase() + ":" + h.value))
  141. ++matches;
  142. }
  143. return matches;
  144. }
  145. function multipartBoundary(headers) {
  146. const contentType = headers.find((h) => h.name.toLowerCase() === "content-type");
  147. if (!contentType?.value.includes("multipart/form-data"))
  148. return void 0;
  149. const boundary = contentType.value.match(/boundary=(\S+)/);
  150. if (boundary)
  151. return boundary[1];
  152. return void 0;
  153. }
  154. // Annotate the CommonJS export names for ESM import in node:
  155. 0 && (module.exports = {
  156. HarBackend
  157. });