@js-recon/js-recon 1.4.2 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-prettify.yaml +6 -6
- package/.github/workflows/rules-smoke-test.yaml +2 -2
- package/CHANGELOG.md +30 -0
- package/Dockerfile +13 -2
- package/Dockerfile.release +12 -2
- package/Dockerfile.w-rules +0 -1
- package/README.md +52 -0
- package/build/analyze/helpers/initRules.js +34 -16
- package/build/analyze/helpers/initRules.js.map +1 -1
- package/build/completion/index.js +44 -3
- package/build/completion/index.js.map +1 -1
- package/build/config/applicationConfig.js +493 -0
- package/build/config/applicationConfig.js.map +1 -0
- package/build/fingerprint/index.js +8 -19
- package/build/fingerprint/index.js.map +1 -1
- package/build/globalConfig.js +1 -1
- package/build/index.js +62 -9
- package/build/index.js.map +1 -1
- package/build/lazyLoad/downloadFilesUtil.js +40 -32
- package/build/lazyLoad/downloadFilesUtil.js.map +1 -1
- package/build/lazyLoad/downloadQueue.js +150 -50
- package/build/lazyLoad/downloadQueue.js.map +1 -1
- package/build/lazyLoad/downloadResponse.js +112 -0
- package/build/lazyLoad/downloadResponse.js.map +1 -0
- package/build/lazyLoad/globals.js +4 -0
- package/build/lazyLoad/globals.js.map +1 -1
- package/build/lazyLoad/index.js +188 -115
- package/build/lazyLoad/index.js.map +1 -1
- package/build/lazyLoad/next_js/NextJsCrawler.js +3 -2
- package/build/lazyLoad/next_js/NextJsCrawler.js.map +1 -1
- package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +8 -9
- package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
- package/build/lazyLoad/next_js/next_SubsequentRequests.js +93 -93
- package/build/lazyLoad/next_js/next_SubsequentRequests.js.map +1 -1
- package/build/lazyLoad/outputPath.js +92 -0
- package/build/lazyLoad/outputPath.js.map +1 -0
- package/build/lazyLoad/react/react_followImports.js +15 -5
- package/build/lazyLoad/react/react_followImports.js.map +1 -1
- package/build/lazyLoad/react/react_getScriptTags.js +11 -6
- package/build/lazyLoad/react/react_getScriptTags.js.map +1 -1
- package/build/lazyLoad/react/react_sourcemapUrls.js +3 -2
- package/build/lazyLoad/react/react_sourcemapUrls.js.map +1 -1
- package/build/lazyLoad/react/react_webpackChunkPaths.js +2 -1
- package/build/lazyLoad/react/react_webpackChunkPaths.js.map +1 -1
- package/build/lazyLoad/techDetect/index.js +93 -59
- package/build/lazyLoad/techDetect/index.js.map +1 -1
- package/build/lazyLoad/vue/vue_RuntimeJs.js +20 -15
- package/build/lazyLoad/vue/vue_RuntimeJs.js.map +1 -1
- package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js +8 -3
- package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js.map +1 -1
- package/build/lazyLoad/vue/vue_discoverJsFiles.js +10 -9
- package/build/lazyLoad/vue/vue_discoverJsFiles.js.map +1 -1
- package/build/lazyLoad/vue/vue_getClientSidePaths.js +69 -45
- package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
- package/build/lazyLoad/vue/vue_jsImports.js +3 -2
- package/build/lazyLoad/vue/vue_jsImports.js.map +1 -1
- package/build/lazyLoad/vue/vue_pageSrc.js +2 -1
- package/build/lazyLoad/vue/vue_pageSrc.js.map +1 -1
- package/build/lazyLoad/vue/vue_reconstructSourceMaps.js +4 -3
- package/build/lazyLoad/vue/vue_reconstructSourceMaps.js.map +1 -1
- package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js +9 -20
- package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js.map +1 -1
- package/build/lazyLoad/vue/vue_severalJsFilesHome.js +2 -1
- package/build/lazyLoad/vue/vue_severalJsFilesHome.js.map +1 -1
- package/build/lazyLoad/vue/vue_stringJsFiles.js +28 -25
- package/build/lazyLoad/vue/vue_stringJsFiles.js.map +1 -1
- package/build/lazyLoad/vue/vue_viteMapDeps.js +8 -2
- package/build/lazyLoad/vue/vue_viteMapDeps.js.map +1 -1
- package/build/proxy/checkFeasibility.js +8 -4
- package/build/proxy/checkFeasibility.js.map +1 -1
- package/build/proxy/checkFireWallBlocking.js +23 -13
- package/build/proxy/checkFireWallBlocking.js.map +1 -1
- package/build/proxy/detectBlockedResponse.js +203 -0
- package/build/proxy/detectBlockedResponse.js.map +1 -0
- package/build/proxy/genReq.js +434 -127
- package/build/proxy/genReq.js.map +1 -1
- package/build/proxy/oxylabsFallback.js +110 -0
- package/build/proxy/oxylabsFallback.js.map +1 -0
- package/build/proxy/proxyFeasibilityRequest.js +11 -9
- package/build/proxy/proxyFeasibilityRequest.js.map +1 -1
- package/build/run/cancellableStep.js +27 -0
- package/build/run/cancellableStep.js.map +1 -0
- package/build/run/index.js +80 -85
- package/build/run/index.js.map +1 -1
- package/build/run/outputDirectory.js +377 -0
- package/build/run/outputDirectory.js.map +1 -0
- package/build/sourcemaps/index.js +109 -23
- package/build/sourcemaps/index.js.map +1 -1
- package/build/strings/index.js +12 -5
- package/build/strings/index.js.map +1 -1
- package/build/utility/downloadProgress.js +99 -0
- package/build/utility/downloadProgress.js.map +1 -0
- package/build/utility/makeReq.js +490 -257
- package/build/utility/makeReq.js.map +1 -1
- package/build/utility/progressLog.js +64 -4
- package/build/utility/progressLog.js.map +1 -1
- package/build/utility/targetInputs.js +177 -0
- package/build/utility/targetInputs.js.map +1 -0
- package/build/utility/urlUtils.js +37 -1
- package/build/utility/urlUtils.js.map +1 -1
- package/config.dist.yaml +251 -0
- package/docker-entrypoint.sh +16 -0
- package/package.json +2 -2
package/build/utility/makeReq.js
CHANGED
|
@@ -23,14 +23,18 @@ import { ProxyAgent, Socks5ProxyAgent } from "undici";
|
|
|
23
23
|
import puppeteer from "./puppeteerInstance.js";
|
|
24
24
|
import { getChromiumPath } from "./getChromiumPath.js";
|
|
25
25
|
import * as globals from "./globals.js";
|
|
26
|
-
import {
|
|
26
|
+
import { getWithMetadata } from "../proxy/genReq.js";
|
|
27
27
|
import checkFireWallBlocking from "../proxy/checkFireWallBlocking.js";
|
|
28
28
|
import { parseProxyUrl } from "../proxy/genericProxy.js";
|
|
29
29
|
import { buildOxylabsProxyUrl, composeOxylabsUsername } from "../proxy/oxylabsProxy.js";
|
|
30
30
|
import fs from "fs";
|
|
31
31
|
import { EventEmitter } from "events";
|
|
32
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
32
34
|
import { progressError, progressLog } from "./progressLog.js";
|
|
33
35
|
import { isSigintHandlerActive } from "../run/interruptHandler.js";
|
|
36
|
+
import detectBlockedResponse from "../proxy/detectBlockedResponse.js";
|
|
37
|
+
import { claimOxylabsFallback, getOxylabsFallbackConfiguration } from "../proxy/oxylabsFallback.js";
|
|
34
38
|
/**
|
|
35
39
|
* Proxy-wiring layer. This is the single place a resolved proxy config (see
|
|
36
40
|
* `../proxy/resolveProxyConfig.ts`) is turned into an actual dispatcher/launch args — every
|
|
@@ -45,11 +49,6 @@ export const getResolvedProxyConfigFromGlobals = () => {
|
|
|
45
49
|
oxylabs: globals.getOxylabsConfig(),
|
|
46
50
|
};
|
|
47
51
|
};
|
|
48
|
-
/** Merges a proxy dispatcher (derived from the current globals) into fetch options, if a proxy is configured. */
|
|
49
|
-
export const withProxyDispatcher = (opts = {}) => {
|
|
50
|
-
const dispatcher = buildUndiciDispatcher(getResolvedProxyConfigFromGlobals());
|
|
51
|
-
return dispatcher ? Object.assign(Object.assign({}, opts), { dispatcher }) : opts;
|
|
52
|
-
};
|
|
53
52
|
/**
|
|
54
53
|
* Builds an undici dispatcher for the resolved proxy config, to pass as `fetch(url, { dispatcher })`.
|
|
55
54
|
* Returns null for the `aws` method (routed separately via genReq.ts) or no proxy configured.
|
|
@@ -74,6 +73,34 @@ export const buildUndiciDispatcher = (resolved) => {
|
|
|
74
73
|
}
|
|
75
74
|
return null;
|
|
76
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Runs request work with an owned dispatcher and always releases its sockets.
|
|
78
|
+
* The callback must consume response bodies before returning.
|
|
79
|
+
*/
|
|
80
|
+
export const withManagedDispatcher = (dispatcher, work) => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
|
+
if (!dispatcher)
|
|
82
|
+
return yield work({});
|
|
83
|
+
let completed = false;
|
|
84
|
+
try {
|
|
85
|
+
const result = yield work({ dispatcher });
|
|
86
|
+
completed = true;
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
const cleanup = completed ? dispatcher.close() : dispatcher.destroy();
|
|
91
|
+
yield cleanup.catch(() => undefined);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
/** Runs request work through the globally active proxy, when proxy use is enabled. */
|
|
95
|
+
export const withActiveProxyDispatcher = (work) => __awaiter(void 0, void 0, void 0, function* () {
|
|
96
|
+
const dispatcher = globals.getUseProxy() ? buildUndiciDispatcher(getResolvedProxyConfigFromGlobals()) : null;
|
|
97
|
+
return yield withManagedDispatcher(dispatcher, work);
|
|
98
|
+
});
|
|
99
|
+
const requestSignalContext = new AsyncLocalStorage();
|
|
100
|
+
/** Supplies cancellation to every makeRequest call in the async work scope. */
|
|
101
|
+
export const withRequestSignal = (signal, work) => requestSignalContext.run(signal, work);
|
|
102
|
+
/** True when the current async request scope has been cancelled. */
|
|
103
|
+
export const isRequestCancelled = () => { var _a; return ((_a = requestSignalContext.getStore()) === null || _a === void 0 ? void 0 : _a.aborted) === true; };
|
|
77
104
|
/**
|
|
78
105
|
* Builds the Puppeteer `--proxy-server=` launch arg plus optional `page.authenticate()` credentials.
|
|
79
106
|
* `aws` never applies here (Puppeteer never went through API Gateway).
|
|
@@ -102,11 +129,9 @@ export const buildPuppeteerProxyArgs = (resolved) => {
|
|
|
102
129
|
}
|
|
103
130
|
return { arg: null };
|
|
104
131
|
};
|
|
105
|
-
|
|
132
|
+
/** Returns browser proxy arguments only when proxy routing is currently enabled. */
|
|
133
|
+
export const getActivePuppeteerProxyArgs = () => globals.getUseProxy() ? buildPuppeteerProxyArgs(getResolvedProxyConfigFromGlobals()) : { arg: null };
|
|
106
134
|
const reportFailure = (url, err) => {
|
|
107
|
-
if (reportedFailures.has(url))
|
|
108
|
-
return;
|
|
109
|
-
reportedFailures.add(url);
|
|
110
135
|
if (globals.getCacheOnly()) {
|
|
111
136
|
progressError(chalk.dim(`[!] Cache miss (cache-only mode): ${url}`));
|
|
112
137
|
return;
|
|
@@ -116,10 +141,26 @@ const reportFailure = (url, err) => {
|
|
|
116
141
|
progressLog(chalk.dim("[i] Often, using -k flag (ignore SSL errors) fixes the problem"));
|
|
117
142
|
}
|
|
118
143
|
};
|
|
144
|
+
const reportTimeout = (url, requestTimeout) => {
|
|
145
|
+
progressError(chalk.red(`[!] Request to ${url} timed out after ${requestTimeout}ms`));
|
|
146
|
+
};
|
|
147
|
+
const reportInvalidUrl = (url) => {
|
|
148
|
+
progressError(chalk.red(`[!] Invalid request URL: ${url}`));
|
|
149
|
+
};
|
|
119
150
|
// random user agents
|
|
120
151
|
const UAs = [
|
|
121
152
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36",
|
|
122
153
|
];
|
|
154
|
+
const getCacheIdentityDigest = (url, headers) => {
|
|
155
|
+
const normalizedHeaders = [...new Headers(headers).entries()].sort(([left], [right]) => left.localeCompare(right));
|
|
156
|
+
return createHash("sha256")
|
|
157
|
+
.update(JSON.stringify([url, normalizedHeaders]))
|
|
158
|
+
.digest("hex");
|
|
159
|
+
};
|
|
160
|
+
const getCacheEntryPath = (url, headers) => {
|
|
161
|
+
const digest = getCacheIdentityDigest(url, headers);
|
|
162
|
+
return `${globals.getRespCacheFile()}.entries/${digest}.json`;
|
|
163
|
+
};
|
|
123
164
|
/**
|
|
124
165
|
* Reads response data from cache for future requests.
|
|
125
166
|
*
|
|
@@ -135,6 +176,23 @@ const UAs = [
|
|
|
135
176
|
* @returns A Promise that resolves to a Response object if the cache is found, or null if not
|
|
136
177
|
*/
|
|
137
178
|
const readCache = (url, headers) => __awaiter(void 0, void 0, void 0, function* () {
|
|
179
|
+
const identityDigest = getCacheIdentityDigest(url, headers);
|
|
180
|
+
const entryPath = getCacheEntryPath(url, headers);
|
|
181
|
+
if (fs.existsSync(entryPath)) {
|
|
182
|
+
try {
|
|
183
|
+
const entry = JSON.parse(fs.readFileSync(entryPath, "utf-8"));
|
|
184
|
+
if (entry.identityDigest === identityDigest) {
|
|
185
|
+
return new Response(Buffer.from(entry.bodyBase64, "base64"), {
|
|
186
|
+
status: entry.status,
|
|
187
|
+
statusText: entry.statusText,
|
|
188
|
+
headers: entry.responseHeaders,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (_a) {
|
|
193
|
+
// Ignore corrupt sidecar entries and fall back to the legacy cache.
|
|
194
|
+
}
|
|
195
|
+
}
|
|
138
196
|
// first, check if the file exists or not
|
|
139
197
|
if (!fs.existsSync(globals.getRespCacheFile())) {
|
|
140
198
|
return null;
|
|
@@ -145,13 +203,12 @@ const readCache = (url, headers) => __awaiter(void 0, void 0, void 0, function*
|
|
|
145
203
|
try {
|
|
146
204
|
cache = JSON.parse(fs.readFileSync(globals.getRespCacheFile(), "utf-8"));
|
|
147
205
|
}
|
|
148
|
-
catch (
|
|
206
|
+
catch (_b) {
|
|
149
207
|
return null;
|
|
150
208
|
}
|
|
151
209
|
if (cache[url]) {
|
|
152
210
|
// check if the response contains the specific request headers
|
|
153
211
|
// iterate through cache[url] and build a Response
|
|
154
|
-
let headersMatch = true;
|
|
155
212
|
// first check if the essential headers match
|
|
156
213
|
const rscEnabled = headers["RSC"] ? true : false;
|
|
157
214
|
if (rscEnabled) {
|
|
@@ -175,139 +232,140 @@ const readCache = (url, headers) => __awaiter(void 0, void 0, void 0, function*
|
|
|
175
232
|
/**
|
|
176
233
|
* Writes response data to cache for future requests.
|
|
177
234
|
*
|
|
178
|
-
* Stores
|
|
179
|
-
*
|
|
235
|
+
* Stores one atomic sidecar entry per URL/request-header identity. The legacy
|
|
236
|
+
* JSON cache remains readable, but new writes avoid whole-cache rewrites.
|
|
180
237
|
*
|
|
181
238
|
* @param url - The URL to cache the response for
|
|
182
239
|
* @param headers - Request headers that were used
|
|
183
240
|
* @param response - The Response object to cache
|
|
184
241
|
* @returns Promise that resolves when caching is complete
|
|
185
242
|
*/
|
|
186
|
-
const writeCache = (
|
|
243
|
+
const writeCache = (url_1, headers_1, response_1, ...args_1) => __awaiter(void 0, [url_1, headers_1, response_1, ...args_1], void 0, function* (url, headers, response, reportErrors = true, signal) {
|
|
244
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
245
|
+
return;
|
|
187
246
|
try {
|
|
188
|
-
yield writeCacheUnsafe(url, headers, response);
|
|
247
|
+
yield writeCacheUnsafe(url, headers, response, signal);
|
|
189
248
|
}
|
|
190
249
|
catch (err) {
|
|
191
250
|
// Caching must never break a request. Log and move on.
|
|
192
|
-
|
|
251
|
+
if (reportErrors) {
|
|
252
|
+
progressError(chalk.yellow(`[!] Failed to write response cache for ${url}: ${(err === null || err === void 0 ? void 0 : err.message) || err}`));
|
|
253
|
+
}
|
|
193
254
|
}
|
|
194
255
|
});
|
|
195
|
-
const writeCacheUnsafe = (url, headers, response) => __awaiter(void 0, void 0, void 0, function* () {
|
|
196
|
-
|
|
197
|
-
const clonedResponse = response.clone();
|
|
198
|
-
// if cache exists, return
|
|
199
|
-
if ((yield readCache(url, headers)) !== null) {
|
|
200
|
-
// console.log("cache already exists for ", url);
|
|
256
|
+
const writeCacheUnsafe = (url, headers, response, signal) => __awaiter(void 0, void 0, void 0, function* () {
|
|
257
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
201
258
|
return;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
catch (_a) {
|
|
209
|
-
cache = {};
|
|
210
|
-
}
|
|
211
|
-
if (!cache[url]) {
|
|
212
|
-
cache[url] = {};
|
|
213
|
-
}
|
|
259
|
+
const identityDigest = getCacheIdentityDigest(url, headers);
|
|
260
|
+
const entryPath = getCacheEntryPath(url, headers);
|
|
261
|
+
if (fs.existsSync(entryPath))
|
|
262
|
+
return;
|
|
263
|
+
const clonedResponse = response.clone();
|
|
214
264
|
const bodyBuffer = Buffer.from(yield clonedResponse.arrayBuffer());
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
else {
|
|
228
|
-
cache[url].normal = {
|
|
229
|
-
req_headers: headers,
|
|
230
|
-
status: status,
|
|
231
|
-
body_b64: body,
|
|
232
|
-
resp_headers: resp_headers,
|
|
233
|
-
};
|
|
234
|
-
// console.log("normal", url);
|
|
235
|
-
}
|
|
236
|
-
let serialized;
|
|
265
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
266
|
+
return;
|
|
267
|
+
const entry = Object.freeze({
|
|
268
|
+
identityDigest,
|
|
269
|
+
status: clonedResponse.status,
|
|
270
|
+
statusText: clonedResponse.statusText,
|
|
271
|
+
bodyBase64: bodyBuffer.toString("base64"),
|
|
272
|
+
responseHeaders: Object.freeze(Object.fromEntries(clonedResponse.headers.entries())),
|
|
273
|
+
});
|
|
274
|
+
const entryDirectory = `${globals.getRespCacheFile()}.entries`;
|
|
275
|
+
fs.mkdirSync(entryDirectory, { recursive: true });
|
|
276
|
+
const temporaryPath = `${entryPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
237
277
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
278
|
+
fs.writeFileSync(temporaryPath, JSON.stringify(entry));
|
|
279
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
280
|
+
return;
|
|
281
|
+
fs.renameSync(temporaryPath, entryPath);
|
|
240
282
|
}
|
|
241
|
-
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
// chalk.yellow(
|
|
245
|
-
// `[!] Response cache too large to serialize; dropping entry for ${url} and skipping cache write`
|
|
246
|
-
// )
|
|
247
|
-
// );
|
|
248
|
-
// // Roll back the entry we just added so future writes don't keep retrying.
|
|
249
|
-
// if (headers["RSC"]) {
|
|
250
|
-
// delete cache[url].rsc;
|
|
251
|
-
// } else {
|
|
252
|
-
// delete cache[url].normal;
|
|
253
|
-
// }
|
|
254
|
-
// if (!cache[url].rsc && !cache[url].normal) {
|
|
255
|
-
// delete cache[url];
|
|
256
|
-
// }
|
|
257
|
-
// return;
|
|
258
|
-
progressError(chalk.yellow(`[!] Cache too big... Emptying cache`));
|
|
259
|
-
serialized = JSON.stringify({}); // empty cache
|
|
260
|
-
}
|
|
261
|
-
// throw err;
|
|
283
|
+
finally {
|
|
284
|
+
if (fs.existsSync(temporaryPath))
|
|
285
|
+
fs.unlinkSync(temporaryPath);
|
|
262
286
|
}
|
|
263
|
-
fs.writeFileSync(globals.getRespCacheFile(), serialized);
|
|
264
|
-
// console.log("wrote cache for ", url);
|
|
265
287
|
});
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
*
|
|
269
|
-
* @param url - The URL to request
|
|
270
|
-
* @param requestOptions - Request options including headers
|
|
271
|
-
* @param requestTimeout - Timeout in milliseconds
|
|
272
|
-
* @returns A Promise that resolves to a Response, or null if all retries fail
|
|
273
|
-
*/
|
|
274
|
-
const singleFetch = (url, requestOptions, requestTimeout) => __awaiter(void 0, void 0, void 0, function* () {
|
|
275
|
-
let res;
|
|
288
|
+
const singleFetch = (url_1, requestOptions_1, requestTimeout_1, ...args_1) => __awaiter(void 0, [url_1, requestOptions_1, requestTimeout_1, ...args_1], void 0, function* (url, requestOptions, requestTimeout, reportErrors = true, dispatcherOverride, maxAttempts = 11) {
|
|
289
|
+
var _a, _b;
|
|
276
290
|
let counter = 0;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
291
|
+
let shouldDestroyOwnedDispatcher = false;
|
|
292
|
+
const ownedDispatcher = dispatcherOverride || !globals.getUseProxy()
|
|
293
|
+
? null
|
|
294
|
+
: buildUndiciDispatcher(getResolvedProxyConfigFromGlobals());
|
|
295
|
+
const dispatcher = (_a = dispatcherOverride !== null && dispatcherOverride !== void 0 ? dispatcherOverride : ownedDispatcher) !== null && _a !== void 0 ? _a : undefined;
|
|
296
|
+
try {
|
|
297
|
+
while (true) {
|
|
298
|
+
const controller = new AbortController();
|
|
299
|
+
let timedOut = false;
|
|
300
|
+
const timeoutId = setTimeout(() => {
|
|
301
|
+
timedOut = true;
|
|
302
|
+
controller.abort();
|
|
303
|
+
}, requestTimeout);
|
|
304
|
+
const upstreamSignal = requestOptions.signal;
|
|
305
|
+
const abortFromUpstream = () => controller.abort();
|
|
306
|
+
if (upstreamSignal === null || upstreamSignal === void 0 ? void 0 : upstreamSignal.aborted) {
|
|
307
|
+
shouldDestroyOwnedDispatcher = true;
|
|
308
|
+
controller.abort();
|
|
309
|
+
clearTimeout(timeoutId);
|
|
310
|
+
return null;
|
|
287
311
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
clearTimeout(timeoutId);
|
|
291
|
-
counter++;
|
|
292
|
-
// BUG: https://github.com/nodejs/node/issues/47246
|
|
293
|
-
if (err.cause && err.cause.code === "UND_ERR_HEADERS_OVERFLOW") {
|
|
294
|
-
progressError(chalk.yellow(`[!] The tool detected a header overflow. Please increase the limit by setting environment variable \`NODE_OPTIONS="--max-http-header-size=99999999"\`. If the error still persists, please try again with a higher limit.`));
|
|
295
|
-
process.exit(21);
|
|
312
|
+
else {
|
|
313
|
+
upstreamSignal === null || upstreamSignal === void 0 ? void 0 : upstreamSignal.addEventListener("abort", abortFromUpstream, { once: true });
|
|
296
314
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
315
|
+
const currentRequestOptions = Object.assign(Object.assign(Object.assign({}, requestOptions), { signal: controller.signal }), (dispatcher ? { dispatcher } : {}));
|
|
316
|
+
try {
|
|
317
|
+
EventEmitter.defaultMaxListeners = 20;
|
|
318
|
+
const response = yield fetch(url, currentRequestOptions);
|
|
319
|
+
const arrayBuffer = yield response.arrayBuffer();
|
|
320
|
+
if (controller.signal.aborted) {
|
|
321
|
+
shouldDestroyOwnedDispatcher = true;
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
const body = Buffer.alloc(arrayBuffer.byteLength);
|
|
325
|
+
body.set(new Uint8Array(arrayBuffer));
|
|
326
|
+
return {
|
|
327
|
+
body,
|
|
328
|
+
status: response.status,
|
|
329
|
+
headers: new Headers(response.headers),
|
|
330
|
+
ok: response.ok,
|
|
331
|
+
text: body.toString("utf-8"),
|
|
332
|
+
};
|
|
300
333
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
334
|
+
catch (err) {
|
|
335
|
+
counter++;
|
|
336
|
+
// BUG: https://github.com/nodejs/node/issues/47246
|
|
337
|
+
if (err.cause && err.cause.code === "UND_ERR_HEADERS_OVERFLOW") {
|
|
338
|
+
progressError(chalk.yellow(`[!] The tool detected a header overflow. Please increase the limit by setting environment variable \`NODE_OPTIONS="--max-http-header-size=99999999"\`. If the error still persists, please try again with a higher limit.`));
|
|
339
|
+
process.exit(21);
|
|
340
|
+
}
|
|
341
|
+
if (err.name === "AbortError") {
|
|
342
|
+
shouldDestroyOwnedDispatcher = true;
|
|
343
|
+
if (reportErrors && (timedOut || !(upstreamSignal === null || upstreamSignal === void 0 ? void 0 : upstreamSignal.aborted)))
|
|
344
|
+
reportTimeout(url, requestTimeout);
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
if (counter >= maxAttempts) {
|
|
348
|
+
shouldDestroyOwnedDispatcher = true;
|
|
349
|
+
if (reportErrors)
|
|
350
|
+
reportFailure(url, err);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
yield new Promise((resolve) => setTimeout(resolve, 500));
|
|
354
|
+
}
|
|
355
|
+
finally {
|
|
356
|
+
clearTimeout(timeoutId);
|
|
357
|
+
upstreamSignal === null || upstreamSignal === void 0 ? void 0 : upstreamSignal.removeEventListener("abort", abortFromUpstream);
|
|
304
358
|
}
|
|
305
|
-
// sleep 0.5 s before retrying
|
|
306
|
-
yield new Promise((resolve) => setTimeout(resolve, 500));
|
|
307
|
-
continue;
|
|
308
359
|
}
|
|
309
360
|
}
|
|
310
|
-
|
|
361
|
+
finally {
|
|
362
|
+
if (ownedDispatcher) {
|
|
363
|
+
const cleanup = shouldDestroyOwnedDispatcher || ((_b = requestOptions.signal) === null || _b === void 0 ? void 0 : _b.aborted)
|
|
364
|
+
? ownedDispatcher.destroy()
|
|
365
|
+
: ownedDispatcher.close();
|
|
366
|
+
yield cleanup.catch(() => undefined);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
311
369
|
});
|
|
312
370
|
/**
|
|
313
371
|
* Handles firewall detection and bypass using headless browser.
|
|
@@ -316,58 +374,65 @@ const singleFetch = (url, requestOptions, requestTimeout) => __awaiter(void 0, v
|
|
|
316
374
|
* @param resp_text - The response text to check for firewall signatures
|
|
317
375
|
* @returns A Promise that resolves to Response content if firewall detected, or null if not
|
|
318
376
|
*/
|
|
319
|
-
const handleFirewall = (
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
377
|
+
const handleFirewall = (url_1, responseText_1, responseMetadata_1, signal_1, ...args_1) => __awaiter(void 0, [url_1, responseText_1, responseMetadata_1, signal_1, ...args_1], void 0, function* (url, responseText, responseMetadata, signal, reportErrors = true) {
|
|
378
|
+
var _a;
|
|
379
|
+
const detection = detectBlockedResponse({
|
|
380
|
+
status: (_a = responseMetadata === null || responseMetadata === void 0 ? void 0 : responseMetadata.status) !== null && _a !== void 0 ? _a : 200,
|
|
381
|
+
headers: responseMetadata === null || responseMetadata === void 0 ? void 0 : responseMetadata.headers,
|
|
382
|
+
body: responseText,
|
|
383
|
+
});
|
|
384
|
+
if (!detection.blocked || (signal === null || signal === void 0 ? void 0 : signal.aborted))
|
|
385
|
+
return null;
|
|
386
|
+
const firewall = detection.provider === "cloudfront" ? "CloudFront" : "Cloudflare/CDN";
|
|
387
|
+
if (reportErrors) {
|
|
388
|
+
progressError(chalk.yellow(`[!] ${firewall} Firewall detected. Trying to bypass with headless browser`));
|
|
389
|
+
}
|
|
390
|
+
const chromiumPath = getChromiumPath();
|
|
391
|
+
const proxyArgs = getActivePuppeteerProxyArgs();
|
|
392
|
+
const browser = yield puppeteer.launch({
|
|
393
|
+
headless: true,
|
|
394
|
+
executablePath: chromiumPath,
|
|
395
|
+
args: [
|
|
396
|
+
...(globals.getDisableSandbox()
|
|
397
|
+
? ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
|
|
398
|
+
: []),
|
|
399
|
+
...(proxyArgs.arg ? [proxyArgs.arg] : []),
|
|
400
|
+
],
|
|
401
|
+
handleSIGINT: !isSigintHandlerActive(),
|
|
402
|
+
});
|
|
403
|
+
const abortBrowser = () => {
|
|
404
|
+
void browser.close().catch(() => undefined);
|
|
405
|
+
};
|
|
406
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", abortBrowser, { once: true });
|
|
407
|
+
try {
|
|
408
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
409
|
+
return null;
|
|
335
410
|
const page = yield browser.newPage();
|
|
336
|
-
if (proxyArgs.authenticate)
|
|
411
|
+
if (proxyArgs.authenticate)
|
|
337
412
|
yield page.authenticate(proxyArgs.authenticate);
|
|
338
|
-
}
|
|
339
413
|
yield page.goto(url);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
yield
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
headless: true,
|
|
351
|
-
executablePath: chromiumPath2,
|
|
352
|
-
args: [
|
|
353
|
-
...(globals.getDisableSandbox()
|
|
354
|
-
? ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"]
|
|
355
|
-
: []),
|
|
356
|
-
...(proxyArgs.arg ? [proxyArgs.arg] : []),
|
|
357
|
-
],
|
|
358
|
-
handleSIGINT: !isSigintHandlerActive(),
|
|
414
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
415
|
+
return null;
|
|
416
|
+
yield new Promise((resolve) => {
|
|
417
|
+
const finish = () => {
|
|
418
|
+
clearTimeout(timeout);
|
|
419
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", finish);
|
|
420
|
+
resolve();
|
|
421
|
+
};
|
|
422
|
+
const timeout = setTimeout(finish, 5000);
|
|
423
|
+
signal === null || signal === void 0 ? void 0 : signal.addEventListener("abort", finish, { once: true });
|
|
359
424
|
});
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
425
|
+
return (signal === null || signal === void 0 ? void 0 : signal.aborted) ? null : yield page.content();
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
429
|
+
return null;
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
finally {
|
|
433
|
+
signal === null || signal === void 0 ? void 0 : signal.removeEventListener("abort", abortBrowser);
|
|
434
|
+
yield browser.close().catch(() => undefined);
|
|
369
435
|
}
|
|
370
|
-
return null;
|
|
371
436
|
});
|
|
372
437
|
/**
|
|
373
438
|
* Makes a GET request to the given URL and returns the response.
|
|
@@ -387,13 +452,22 @@ const handleFirewall = (url, resp_text) => __awaiter(void 0, void 0, void 0, fun
|
|
|
387
452
|
* @returns A Promise that resolves to a Response, or null if all retries fail
|
|
388
453
|
*/
|
|
389
454
|
const makeRequest = (url, args) => __awaiter(void 0, void 0, void 0, function* () {
|
|
455
|
+
var _a, _b, _c, _d, _e;
|
|
390
456
|
if (url.startsWith("//")) {
|
|
391
457
|
url = "https:" + url;
|
|
392
458
|
}
|
|
393
|
-
const
|
|
394
|
-
const
|
|
459
|
+
const _f = args || {}, { timeout, reportErrors = true } = _f, restArgs = __rest(_f, ["timeout", "reportErrors"]);
|
|
460
|
+
const contextualSignal = requestSignalContext.getStore();
|
|
461
|
+
const explicitSignal = (_a = restArgs.signal) !== null && _a !== void 0 ? _a : undefined;
|
|
462
|
+
const effectiveSignal = contextualSignal && explicitSignal && contextualSignal !== explicitSignal
|
|
463
|
+
? AbortSignal.any([contextualSignal, explicitSignal])
|
|
464
|
+
: (explicitSignal !== null && explicitSignal !== void 0 ? explicitSignal : contextualSignal);
|
|
465
|
+
const isCancelled = () => (effectiveSignal === null || effectiveSignal === void 0 ? void 0 : effectiveSignal.aborted) === true;
|
|
466
|
+
if (isCancelled())
|
|
467
|
+
return null;
|
|
395
468
|
const requestTimeout = timeout || globals.getRequestTimeout();
|
|
396
|
-
const usingDefaultHeaders = !
|
|
469
|
+
const usingDefaultHeaders = !restArgs.headers;
|
|
470
|
+
const requestStartedWithProxy = globals.getUseProxy();
|
|
397
471
|
// Build default headers if not provided
|
|
398
472
|
const baseHeaders = {
|
|
399
473
|
"User-Agent": UAs[Math.floor(Math.random() * UAs.length)],
|
|
@@ -407,63 +481,111 @@ const makeRequest = (url, args) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
407
481
|
try {
|
|
408
482
|
parsedOrigin = new URL(url).origin;
|
|
409
483
|
}
|
|
410
|
-
catch (
|
|
484
|
+
catch (_g) {
|
|
485
|
+
if (reportErrors)
|
|
486
|
+
reportInvalidUrl(url);
|
|
411
487
|
return null;
|
|
412
488
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
489
|
+
const requestOptions = Object.assign(Object.assign(Object.assign({}, restArgs), (effectiveSignal ? { signal: effectiveSignal } : {})), { headers: (_b = restArgs.headers) !== null && _b !== void 0 ? _b : Object.assign(Object.assign({}, baseHeaders), { Referer: parsedOrigin, Origin: parsedOrigin }) });
|
|
490
|
+
// A successful default-header request may have used the no-Referer fallback.
|
|
491
|
+
// Probe both request identities so that response remains reusable on later
|
|
492
|
+
// runs, including cache-only runs that cannot repeat the network fallback.
|
|
493
|
+
const cacheHeaderCandidates = usingDefaultHeaders
|
|
494
|
+
? [requestOptions.headers || {}, baseHeaders]
|
|
495
|
+
: [requestOptions.headers || {}];
|
|
416
496
|
// if cache is enabled, read the cache and return if cache is present. else, continue
|
|
417
497
|
if (!globals.getDisableCache()) {
|
|
418
|
-
|
|
498
|
+
let cachedEntry = null;
|
|
499
|
+
for (const candidateHeaders of cacheHeaderCandidates) {
|
|
500
|
+
const response = yield readCache(url, candidateHeaders);
|
|
501
|
+
if (response !== null) {
|
|
502
|
+
cachedEntry = Object.freeze({ response, headers: candidateHeaders });
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (isCancelled())
|
|
507
|
+
return null;
|
|
419
508
|
// if the response if cached, then check if contains any signatures of firewall
|
|
420
|
-
if (
|
|
421
|
-
const cachedText = yield
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
509
|
+
if (cachedEntry !== null) {
|
|
510
|
+
const cachedText = yield cachedEntry.response.clone().text();
|
|
511
|
+
if (isCancelled())
|
|
512
|
+
return null;
|
|
513
|
+
const cachedDetection = detectBlockedResponse({
|
|
514
|
+
status: cachedEntry.response.status,
|
|
515
|
+
headers: cachedEntry.response.headers,
|
|
516
|
+
body: cachedText,
|
|
517
|
+
});
|
|
518
|
+
const shouldRefreshBlockedCache = !globals.getCacheOnly() &&
|
|
519
|
+
!requestStartedWithProxy &&
|
|
520
|
+
getOxylabsFallbackConfiguration().enabled &&
|
|
521
|
+
cachedDetection.blocked;
|
|
522
|
+
if (shouldRefreshBlockedCache) {
|
|
523
|
+
const sidecarPath = getCacheEntryPath(url, cachedEntry.headers);
|
|
524
|
+
try {
|
|
525
|
+
if (fs.existsSync(sidecarPath))
|
|
526
|
+
fs.unlinkSync(sidecarPath);
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
if (reportErrors) {
|
|
530
|
+
progressError(chalk.yellow(`[!] Could not replace blocked cache entry for ${url}: ${error}`));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
if (globals.getCacheOnly())
|
|
536
|
+
return cachedEntry.response;
|
|
537
|
+
const firewallContent = yield handleFirewall(url, cachedText, { status: cachedEntry.response.status, headers: cachedEntry.response.headers }, effectiveSignal, reportErrors);
|
|
538
|
+
if (isCancelled())
|
|
539
|
+
return null;
|
|
540
|
+
if (firewallContent) {
|
|
541
|
+
yield writeCache(url, cachedEntry.headers, new Response(firewallContent), reportErrors, effectiveSignal);
|
|
542
|
+
if (isCancelled())
|
|
543
|
+
return null;
|
|
544
|
+
return new Response(firewallContent);
|
|
545
|
+
}
|
|
546
|
+
return cachedEntry.response;
|
|
426
547
|
}
|
|
427
|
-
return cachedResponse;
|
|
428
548
|
}
|
|
429
549
|
}
|
|
430
550
|
if (globals.getCacheOnly()) {
|
|
431
|
-
|
|
551
|
+
if (reportErrors)
|
|
552
|
+
reportFailure(url, "cache miss in cache-only mode");
|
|
432
553
|
return null;
|
|
433
554
|
}
|
|
434
555
|
if (globals.getUseProxy() && globals.getProxyMethod() === "aws") {
|
|
435
|
-
const
|
|
436
|
-
|
|
556
|
+
const getHeaders = Object.fromEntries(new Headers(requestOptions.headers).entries());
|
|
557
|
+
let awsResponse;
|
|
558
|
+
try {
|
|
559
|
+
awsResponse = yield getWithMetadata(url, getHeaders, effectiveSignal);
|
|
560
|
+
}
|
|
561
|
+
catch (err) {
|
|
562
|
+
if (isCancelled())
|
|
563
|
+
return null;
|
|
564
|
+
if (reportErrors)
|
|
565
|
+
reportFailure(url, err);
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
if (isCancelled())
|
|
569
|
+
return null;
|
|
437
570
|
// check if any firewall is there in the way
|
|
438
|
-
if (yield checkFireWallBlocking(body)) {
|
|
571
|
+
if (yield checkFireWallBlocking(awsResponse.body, { status: awsResponse.status, headers: awsResponse.headers })) {
|
|
439
572
|
progressError(chalk.magenta("[!] Please try again without API Gateway"));
|
|
440
573
|
process.exit(18);
|
|
441
574
|
}
|
|
442
575
|
// craft a Response, and return that
|
|
443
|
-
const response = new Response(body
|
|
576
|
+
const response = new Response(awsResponse.body, {
|
|
577
|
+
status: (_c = awsResponse.status) !== null && _c !== void 0 ? _c : 200,
|
|
578
|
+
headers: (_d = awsResponse.headers) !== null && _d !== void 0 ? _d : {},
|
|
579
|
+
});
|
|
444
580
|
// if cache is enabled, write the response to the cache
|
|
445
581
|
if (!globals.getDisableCache()) {
|
|
446
|
-
yield writeCache(url,
|
|
582
|
+
yield writeCache(url, getHeaders, response, reportErrors, effectiveSignal);
|
|
447
583
|
}
|
|
584
|
+
if (isCancelled())
|
|
585
|
+
return null;
|
|
448
586
|
return response;
|
|
449
587
|
}
|
|
450
588
|
else {
|
|
451
|
-
// Helper to read response once and store data for reuse.
|
|
452
|
-
// We copy the bytes into a Buffer that owns its own ArrayBuffer so the
|
|
453
|
-
// stored body cannot be invalidated by undici recycling its internal
|
|
454
|
-
// buffer after we consume the response.
|
|
455
|
-
const consumeResponse = (res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
456
|
-
if (!res)
|
|
457
|
-
return null;
|
|
458
|
-
const ab = yield res.arrayBuffer();
|
|
459
|
-
const body = Buffer.alloc(ab.byteLength);
|
|
460
|
-
body.set(new Uint8Array(ab));
|
|
461
|
-
const text = body.toString("utf-8");
|
|
462
|
-
// Snapshot headers as a plain object so we can rebuild a fresh
|
|
463
|
-
// Headers instance per Response instead of sharing state.
|
|
464
|
-
const headers = new Headers(res.headers);
|
|
465
|
-
return { body, status: res.status, headers, ok: res.ok, text };
|
|
466
|
-
});
|
|
467
589
|
// Helper to create Response from stored data. Pass the body as an
|
|
468
590
|
// immutable string so each Response gets a fully independent backing
|
|
469
591
|
// store; undici has been known to flag Responses as already-consumed
|
|
@@ -480,76 +602,187 @@ const makeRequest = (url, args) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
480
602
|
headers.delete("transfer-encoding");
|
|
481
603
|
return new Response(bodyCopy, { status: data.status, headers });
|
|
482
604
|
};
|
|
605
|
+
let oxylabsFallbackAttempted = false;
|
|
606
|
+
const maybeRetryBlockedResponse = (directData, headers) => __awaiter(void 0, void 0, void 0, function* () {
|
|
607
|
+
var _a, _b;
|
|
608
|
+
if (!directData || isCancelled())
|
|
609
|
+
return null;
|
|
610
|
+
if (!getOxylabsFallbackConfiguration().enabled)
|
|
611
|
+
return null;
|
|
612
|
+
const detection = detectBlockedResponse({
|
|
613
|
+
status: directData.status,
|
|
614
|
+
headers: directData.headers,
|
|
615
|
+
body: directData.text,
|
|
616
|
+
});
|
|
617
|
+
if (!detection.blocked)
|
|
618
|
+
return null;
|
|
619
|
+
const method = ((_a = requestOptions.method) !== null && _a !== void 0 ? _a : "GET").toUpperCase();
|
|
620
|
+
if (requestStartedWithProxy || oxylabsFallbackAttempted || !["GET", "HEAD"].includes(method)) {
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
oxylabsFallbackAttempted = true;
|
|
624
|
+
const proxy = claimOxylabsFallback(url);
|
|
625
|
+
if (!proxy || isCancelled())
|
|
626
|
+
return null;
|
|
627
|
+
if (reportErrors) {
|
|
628
|
+
progressLog(chalk.yellow(`[i] ${(_b = detection.provider) !== null && _b !== void 0 ? _b : "CDN/WAF"} block detected; retrying this request through Oxylabs`));
|
|
629
|
+
}
|
|
630
|
+
const dispatcher = buildUndiciDispatcher({ method: "oxylabs", oxylabs: proxy });
|
|
631
|
+
if (!dispatcher)
|
|
632
|
+
return null;
|
|
633
|
+
let fallbackData = null;
|
|
634
|
+
try {
|
|
635
|
+
fallbackData = yield singleFetch(url, Object.assign(Object.assign({}, requestOptions), { headers }), requestTimeout, false, dispatcher, 1);
|
|
636
|
+
}
|
|
637
|
+
finally {
|
|
638
|
+
const cleanup = isCancelled() || !fallbackData ? dispatcher.destroy() : dispatcher.close();
|
|
639
|
+
yield cleanup.catch(() => undefined);
|
|
640
|
+
}
|
|
641
|
+
if (!fallbackData || isCancelled())
|
|
642
|
+
return null;
|
|
643
|
+
const fallbackDetection = detectBlockedResponse({
|
|
644
|
+
status: fallbackData.status,
|
|
645
|
+
headers: fallbackData.headers,
|
|
646
|
+
body: fallbackData.text,
|
|
647
|
+
});
|
|
648
|
+
if (fallbackDetection.blocked || !fallbackData.ok) {
|
|
649
|
+
if (reportErrors) {
|
|
650
|
+
progressError(chalk.yellow("[!] Oxylabs fallback did not bypass the CDN/WAF response"));
|
|
651
|
+
}
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
return fallbackData;
|
|
655
|
+
});
|
|
656
|
+
const finishPlainResponse = (data, headers) => __awaiter(void 0, void 0, void 0, function* () {
|
|
657
|
+
if (!(data === null || data === void 0 ? void 0 : data.ok))
|
|
658
|
+
return null;
|
|
659
|
+
const detection = detectBlockedResponse({
|
|
660
|
+
status: data.status,
|
|
661
|
+
headers: data.headers,
|
|
662
|
+
body: data.text,
|
|
663
|
+
});
|
|
664
|
+
if (detection.blocked)
|
|
665
|
+
return null;
|
|
666
|
+
if (!globals.getDisableCache()) {
|
|
667
|
+
yield writeCache(url, headers, createResponse(data), reportErrors, effectiveSignal);
|
|
668
|
+
}
|
|
669
|
+
return isCancelled() ? null : createResponse(data);
|
|
670
|
+
});
|
|
671
|
+
const tryBrowserRecovery = (blockedData, headers) => __awaiter(void 0, void 0, void 0, function* () {
|
|
672
|
+
if (!(blockedData === null || blockedData === void 0 ? void 0 : blockedData.ok) || isCancelled())
|
|
673
|
+
return null;
|
|
674
|
+
let browserContent;
|
|
675
|
+
try {
|
|
676
|
+
browserContent = yield handleFirewall(url, blockedData.text, { status: blockedData.status, headers: blockedData.headers }, effectiveSignal, reportErrors);
|
|
677
|
+
}
|
|
678
|
+
catch (error) {
|
|
679
|
+
if (reportErrors && !isCancelled()) {
|
|
680
|
+
progressError(chalk.yellow(`[!] Browser firewall bypass failed for ${url}: ${(error === null || error === void 0 ? void 0 : error.message) || error}`));
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
if (!browserContent || isCancelled())
|
|
685
|
+
return null;
|
|
686
|
+
const browserDetection = detectBlockedResponse({
|
|
687
|
+
status: 200,
|
|
688
|
+
headers: { "content-type": "text/html" },
|
|
689
|
+
body: browserContent,
|
|
690
|
+
});
|
|
691
|
+
if (browserDetection.blocked) {
|
|
692
|
+
if (reportErrors) {
|
|
693
|
+
progressError(chalk.yellow("[!] Browser fallback still returned a CDN/WAF challenge"));
|
|
694
|
+
}
|
|
695
|
+
return null;
|
|
696
|
+
}
|
|
697
|
+
const response = new Response(browserContent);
|
|
698
|
+
if (!globals.getDisableCache()) {
|
|
699
|
+
yield writeCache(url, headers, response, reportErrors, effectiveSignal);
|
|
700
|
+
}
|
|
701
|
+
return isCancelled() ? null : new Response(browserContent);
|
|
702
|
+
});
|
|
483
703
|
// When using default headers, try both with and without Referer/Origin
|
|
484
704
|
// to handle servers that return 404 for one but 200 for the other
|
|
485
705
|
if (usingDefaultHeaders) {
|
|
486
706
|
// First try: with Referer and Origin
|
|
487
707
|
const headersWithReferer = Object.assign(Object.assign({}, baseHeaders), { Referer: new URL(url).origin, Origin: new URL(url).origin });
|
|
488
|
-
const
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (!globals.getDisableCache()) {
|
|
495
|
-
yield writeCache(url, headersWithReferer, new Response(firewallContent));
|
|
496
|
-
}
|
|
497
|
-
return new Response(firewallContent);
|
|
498
|
-
}
|
|
499
|
-
// Cache and return successful response
|
|
500
|
-
if (!globals.getDisableCache()) {
|
|
501
|
-
yield writeCache(url, headersWithReferer, createResponse(dataWithReferer));
|
|
502
|
-
}
|
|
503
|
-
return createResponse(dataWithReferer);
|
|
504
|
-
}
|
|
708
|
+
const directDataWithReferer = yield singleFetch(url, Object.assign(Object.assign({}, requestOptions), { headers: headersWithReferer }), requestTimeout, false);
|
|
709
|
+
if (isCancelled())
|
|
710
|
+
return null;
|
|
711
|
+
const withRefererResponse = yield finishPlainResponse(directDataWithReferer, headersWithReferer);
|
|
712
|
+
if (withRefererResponse)
|
|
713
|
+
return withRefererResponse;
|
|
505
714
|
// Second try: without Referer and Origin
|
|
506
715
|
const headersWithoutReferer = Object.assign({}, baseHeaders);
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
716
|
+
const directDataWithoutReferer = yield singleFetch(url, Object.assign(Object.assign({}, requestOptions), { headers: headersWithoutReferer }), requestTimeout, reportErrors && !directDataWithReferer);
|
|
717
|
+
if (isCancelled())
|
|
718
|
+
return null;
|
|
719
|
+
const withoutRefererResponse = yield finishPlainResponse(directDataWithoutReferer, headersWithoutReferer);
|
|
720
|
+
if (withoutRefererResponse)
|
|
721
|
+
return withoutRefererResponse;
|
|
722
|
+
const candidates = [
|
|
723
|
+
{ data: directDataWithoutReferer, headers: headersWithoutReferer },
|
|
724
|
+
{ data: directDataWithReferer, headers: headersWithReferer },
|
|
725
|
+
];
|
|
726
|
+
const blockedCandidate = candidates.find(({ data }) => data &&
|
|
727
|
+
detectBlockedResponse({ status: data.status, headers: data.headers, body: data.text }).blocked);
|
|
728
|
+
if (blockedCandidate) {
|
|
729
|
+
const fallbackData = yield maybeRetryBlockedResponse(blockedCandidate.data, blockedCandidate.headers);
|
|
730
|
+
if (isCancelled())
|
|
731
|
+
return null;
|
|
732
|
+
const fallbackResponse = yield finishPlainResponse(fallbackData, blockedCandidate.headers);
|
|
733
|
+
if (fallbackResponse)
|
|
734
|
+
return fallbackResponse;
|
|
735
|
+
const browserResponse = yield tryBrowserRecovery(blockedCandidate.data, blockedCandidate.headers);
|
|
736
|
+
if (browserResponse)
|
|
737
|
+
return browserResponse;
|
|
523
738
|
}
|
|
524
|
-
//
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
739
|
+
// Prefer the most recent non-blocked response, then any direct response.
|
|
740
|
+
const finalCandidate = (_e = candidates.find(({ data }) => data &&
|
|
741
|
+
!detectBlockedResponse({ status: data.status, headers: data.headers, body: data.text }).blocked)) !== null && _e !== void 0 ? _e : candidates.find(({ data }) => data !== null);
|
|
742
|
+
if (finalCandidate === null || finalCandidate === void 0 ? void 0 : finalCandidate.data) {
|
|
743
|
+
const finalData = finalCandidate.data;
|
|
744
|
+
const finalDetection = detectBlockedResponse({
|
|
745
|
+
status: finalData.status,
|
|
746
|
+
headers: finalData.headers,
|
|
747
|
+
body: finalData.text,
|
|
748
|
+
});
|
|
749
|
+
if (!globals.getDisableCache() && !finalDetection.blocked) {
|
|
750
|
+
yield writeCache(url, finalCandidate.headers, createResponse(finalData), reportErrors, effectiveSignal);
|
|
529
751
|
}
|
|
752
|
+
if (isCancelled())
|
|
753
|
+
return null;
|
|
530
754
|
return createResponse(finalData);
|
|
531
755
|
}
|
|
532
756
|
return null;
|
|
533
757
|
}
|
|
534
758
|
else {
|
|
535
759
|
// Custom headers provided, use them directly
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
if (!data)
|
|
760
|
+
const directData = yield singleFetch(url, requestOptions, requestTimeout, reportErrors);
|
|
761
|
+
if (!directData || isCancelled())
|
|
539
762
|
return null;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
763
|
+
const directResponse = yield finishPlainResponse(directData, requestOptions.headers || {});
|
|
764
|
+
if (directResponse)
|
|
765
|
+
return directResponse;
|
|
766
|
+
const fallbackData = yield maybeRetryBlockedResponse(directData, requestOptions.headers || {});
|
|
767
|
+
if (isCancelled())
|
|
768
|
+
return null;
|
|
769
|
+
const fallbackResponse = yield finishPlainResponse(fallbackData, requestOptions.headers || {});
|
|
770
|
+
if (fallbackResponse)
|
|
771
|
+
return fallbackResponse;
|
|
772
|
+
const browserResponse = yield tryBrowserRecovery(directData, requestOptions.headers || {});
|
|
773
|
+
if (browserResponse)
|
|
774
|
+
return browserResponse;
|
|
775
|
+
const directDetection = detectBlockedResponse({
|
|
776
|
+
status: directData.status,
|
|
777
|
+
headers: directData.headers,
|
|
778
|
+
body: directData.text,
|
|
779
|
+
});
|
|
780
|
+
if (!globals.getDisableCache() && !directDetection.blocked) {
|
|
781
|
+
yield writeCache(url, requestOptions.headers || {}, createResponse(directData), reportErrors, effectiveSignal);
|
|
551
782
|
}
|
|
552
|
-
|
|
783
|
+
if (isCancelled())
|
|
784
|
+
return null;
|
|
785
|
+
return createResponse(directData);
|
|
553
786
|
}
|
|
554
787
|
}
|
|
555
788
|
});
|