UNPKG

rebrowser-playwright-core

Version:

A drop-in replacement for playwright-core patched with rebrowser-patches. It allows to pass modern automation detection tests.

338 lines (335 loc) 14.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BidiNetworkManager = void 0; exports.bidiBytesValueToString = bidiBytesValueToString; var _eventsHelper = require("../../utils/eventsHelper"); var network = _interopRequireWildcard(require("../network")); var bidi = _interopRequireWildcard(require("./third_party/bidiProtocol")); var _cookieStore = require("../cookieStore"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * Copyright (c) Microsoft Corporation. * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an 'AS IS' BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ class BidiNetworkManager { constructor(bidiSession, page, onNavigationResponseStarted) { this._session = void 0; this._requests = void 0; this._page = void 0; this._eventListeners = void 0; this._onNavigationResponseStarted = void 0; this._userRequestInterceptionEnabled = false; this._protocolRequestInterceptionEnabled = false; this._credentials = void 0; this._intercepId = void 0; this._session = bidiSession; this._requests = new Map(); this._page = page; this._onNavigationResponseStarted = onNavigationResponseStarted; this._eventListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.beforeRequestSent', this._onBeforeRequestSent.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseStarted', this._onResponseStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.responseCompleted', this._onResponseCompleted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.fetchError', this._onFetchError.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'network.authRequired', this._onAuthRequired.bind(this))]; } dispose() { _eventsHelper.eventsHelper.removeEventListeners(this._eventListeners); } _onBeforeRequestSent(param) { if (param.request.url.startsWith('data:')) return; const redirectedFrom = param.redirectCount ? this._requests.get(param.request.request) || null : null; const frame = redirectedFrom ? redirectedFrom.request.frame() : param.context ? this._page._frameManager.frame(param.context) : null; if (!frame) return; if (redirectedFrom) this._requests.delete(redirectedFrom._id); let route; if (param.intercepts) { // We do not support intercepting redirects. if (redirectedFrom) { var _redirectedFrom$_orig; this._session.sendMayFail('network.continueRequest', { request: param.request.request, ...(((_redirectedFrom$_orig = redirectedFrom._originalRequestRoute) === null || _redirectedFrom$_orig === void 0 ? void 0 : _redirectedFrom$_orig._alreadyContinuedHeaders) || {}) }); } else { route = new BidiRouteImpl(this._session, param.request.request); } } const request = new BidiRequest(frame, redirectedFrom, param, route); this._requests.set(request._id, request); this._page._frameManager.requestStarted(request.request, route); } _onResponseStarted(params) { const request = this._requests.get(params.request.request); if (!request) return; const getResponseBody = async () => { throw new Error(`Response body is not available for requests in Bidi`); }; const timings = params.request.timings; const startTime = timings.requestTime; function relativeToStart(time) { if (!time) return -1; return (time - startTime) / 1000; } const timing = { startTime: startTime / 1000, requestStart: relativeToStart(timings.requestStart), responseStart: relativeToStart(timings.responseStart), domainLookupStart: relativeToStart(timings.dnsStart), domainLookupEnd: relativeToStart(timings.dnsEnd), connectStart: relativeToStart(timings.connectStart), secureConnectionStart: relativeToStart(timings.tlsStart), connectEnd: relativeToStart(timings.connectEnd) }; const response = new network.Response(request.request, params.response.status, params.response.statusText, fromBidiHeaders(params.response.headers), timing, getResponseBody, false); response._serverAddrFinished(); response._securityDetailsFinished(); // "raw" headers are the same as "provisional" headers in Bidi. response.setRawResponseHeaders(null); response.setResponseHeadersSize(params.response.headersSize); this._page._frameManager.requestReceivedResponse(response); if (params.navigation) this._onNavigationResponseStarted(params); } _onResponseCompleted(params) { const request = this._requests.get(params.request.request); if (!request) return; const response = request.request._existingResponse(); // TODO: body size is the encoded size response.setTransferSize(params.response.bodySize); response.setEncodedBodySize(params.response.bodySize); // Keep redirected requests in the map for future reference as redirectedFrom. const isRedirected = response.status() >= 300 && response.status() <= 399; const responseEndTime = params.request.timings.responseEnd / 1000 - response.timing().startTime; if (isRedirected) { response._requestFinished(responseEndTime); } else { this._requests.delete(request._id); response._requestFinished(responseEndTime); } response._setHttpVersion(params.response.protocol); this._page._frameManager.reportRequestFinished(request.request, response); } _onFetchError(params) { const request = this._requests.get(params.request.request); if (!request) return; this._requests.delete(request._id); const response = request.request._existingResponse(); if (response) { response.setTransferSize(null); response.setEncodedBodySize(null); response._requestFinished(-1); } request.request._setFailureText(params.errorText); // TODO: support canceled flag this._page._frameManager.requestFailed(request.request, params.errorText === 'NS_BINDING_ABORTED'); } _onAuthRequired(params) { var _params$response$auth; const isBasic = (_params$response$auth = params.response.authChallenges) === null || _params$response$auth === void 0 ? void 0 : _params$response$auth.some(challenge => challenge.scheme.startsWith('Basic')); const credentials = this._page._browserContext._options.httpCredentials; if (isBasic && credentials) { this._session.sendMayFail('network.continueWithAuth', { request: params.request.request, action: 'provideCredentials', credentials: { type: 'password', username: credentials.username, password: credentials.password } }); } else { this._session.sendMayFail('network.continueWithAuth', { request: params.request.request, action: 'default' }); } } async setRequestInterception(value) { this._userRequestInterceptionEnabled = value; await this._updateProtocolRequestInterception(); } async setCredentials(credentials) { this._credentials = credentials; await this._updateProtocolRequestInterception(); } async _updateProtocolRequestInterception(initial) { const enabled = this._userRequestInterceptionEnabled || !!this._credentials; if (enabled === this._protocolRequestInterceptionEnabled) return; this._protocolRequestInterceptionEnabled = enabled; if (initial && !enabled) return; const cachePromise = this._session.send('network.setCacheBehavior', { cacheBehavior: enabled ? 'bypass' : 'default' }); let interceptPromise = Promise.resolve(undefined); if (enabled) { interceptPromise = this._session.send('network.addIntercept', { phases: [bidi.Network.InterceptPhase.AuthRequired, bidi.Network.InterceptPhase.BeforeRequestSent], urlPatterns: [{ type: 'pattern' }] // urlPatterns: [{ type: 'string', pattern: '*' }], }).then(r => { this._intercepId = r.intercept; }); } else if (this._intercepId) { interceptPromise = this._session.send('network.removeIntercept', { intercept: this._intercepId }); this._intercepId = undefined; } await Promise.all([cachePromise, interceptPromise]); } } exports.BidiNetworkManager = BidiNetworkManager; class BidiRequest { constructor(frame, redirectedFrom, payload, route) { var _payload$navigation; this.request = void 0; this._id = void 0; this._redirectedTo = void 0; // Only first request in the chain can be intercepted, so this will // store the first and only Route in the chain (if any). this._originalRequestRoute = void 0; this._id = payload.request.request; if (redirectedFrom) redirectedFrom._redirectedTo = this; // TODO: missing in the spec? const postDataBuffer = null; this.request = new network.Request(frame._page._browserContext, frame, null, redirectedFrom ? redirectedFrom.request : null, (_payload$navigation = payload.navigation) !== null && _payload$navigation !== void 0 ? _payload$navigation : undefined, payload.request.url, 'other', payload.request.method, postDataBuffer, fromBidiHeaders(payload.request.headers)); // "raw" headers are the same as "provisional" headers in Bidi. this.request.setRawRequestHeaders(null); this.request._setBodySize(payload.request.bodySize || 0); this._originalRequestRoute = route !== null && route !== void 0 ? route : redirectedFrom === null || redirectedFrom === void 0 ? void 0 : redirectedFrom._originalRequestRoute; route === null || route === void 0 || route._setRequest(this.request); } _finalRequest() { let request = this; while (request._redirectedTo) request = request._redirectedTo; return request; } } class BidiRouteImpl { constructor(session, requestId) { this._requestId = void 0; this._session = void 0; this._request = void 0; this._alreadyContinuedHeaders = void 0; this._session = session; this._requestId = requestId; } _setRequest(request) { this._request = request; } async continue(overrides) { // Firefox does not update content-length header. let headers = overrides.headers || this._request.headers(); if (overrides.postData && headers) { headers = headers.map(header => { if (header.name.toLowerCase() === 'content-length') return { name: header.name, value: overrides.postData.byteLength.toString() }; return header; }); } this._alreadyContinuedHeaders = headers; await this._session.sendMayFail('network.continueRequest', { request: this._requestId, url: overrides.url, method: overrides.method, ...toBidiRequestHeaders(this._alreadyContinuedHeaders), body: overrides.postData ? { type: 'base64', value: Buffer.from(overrides.postData).toString('base64') } : undefined }); } async fulfill(response) { const base64body = response.isBase64 ? response.body : Buffer.from(response.body).toString('base64'); await this._session.sendMayFail('network.provideResponse', { request: this._requestId, statusCode: response.status, reasonPhrase: network.statusText(response.status), ...toBidiResponseHeaders(response.headers), body: { type: 'base64', value: base64body } }); } async abort(errorCode) { await this._session.sendMayFail('network.failRequest', { request: this._requestId }); } } function fromBidiHeaders(bidiHeaders) { const result = []; for (const { name, value } of bidiHeaders) result.push({ name, value: bidiBytesValueToString(value) }); return result; } function toBidiRequestHeaders(allHeaders) { const bidiHeaders = toBidiHeaders(allHeaders); const cookies = bidiHeaders.filter(h => h.name.toLowerCase() === 'cookie'); const headers = bidiHeaders.filter(h => h.name.toLowerCase() !== 'cookie'); return { cookies, headers }; } function toBidiResponseHeaders(headers) { const setCookieHeaders = headers.filter(h => h.name.toLowerCase() === 'set-cookie'); const otherHeaders = headers.filter(h => h.name.toLowerCase() !== 'set-cookie'); const rawCookies = setCookieHeaders.map(h => (0, _cookieStore.parseRawCookie)(h.value)); const cookies = rawCookies.filter(Boolean).map(c => { return { ...c, value: { type: 'string', value: c.value }, sameSite: toBidiSameSite(c.sameSite) }; }); return { cookies, headers: toBidiHeaders(otherHeaders) }; } function toBidiHeaders(headers) { return headers.map(({ name, value }) => ({ name, value: { type: 'string', value } })); } function bidiBytesValueToString(value) { if (value.type === 'string') return value.value; if (value.type === 'base64') return Buffer.from(value.type, 'base64').toString('binary'); return 'unknown value type: ' + value.type; } function toBidiSameSite(sameSite) { if (!sameSite) return undefined; if (sameSite === 'Strict') return bidi.Network.SameSite.Strict; if (sameSite === 'Lax') return bidi.Network.SameSite.Lax; return bidi.Network.SameSite.None; }