fetch.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.GlobalAPIRequestContext = exports.BrowserContextAPIRequestContext = exports.APIRequestContext = void 0;
  6. var http = _interopRequireWildcard(require("http"));
  7. var https = _interopRequireWildcard(require("https"));
  8. var _stream = require("stream");
  9. var _url = _interopRequireDefault(require("url"));
  10. var _zlib = _interopRequireDefault(require("zlib"));
  11. var _timeoutSettings = require("../common/timeoutSettings");
  12. var _userAgent = require("../utils/userAgent");
  13. var _utils = require("../utils");
  14. var _utilsBundle = require("../utilsBundle");
  15. var _browserContext = require("./browserContext");
  16. var _cookieStore = require("./cookieStore");
  17. var _formData = require("./formData");
  18. var _happyEyeballs = require("../utils/happy-eyeballs");
  19. var _instrumentation = require("./instrumentation");
  20. var _progress = require("./progress");
  21. var _tracing = require("./trace/recorder/tracing");
  22. var _network = require("./network");
  23. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  24. function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
  25. function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
  26. /**
  27. * Copyright (c) Microsoft Corporation.
  28. *
  29. * Licensed under the Apache License, Version 2.0 (the "License");
  30. * you may not use this file except in compliance with the License.
  31. * You may obtain a copy of the License at
  32. *
  33. * http://www.apache.org/licenses/LICENSE-2.0
  34. *
  35. * Unless required by applicable law or agreed to in writing, software
  36. * distributed under the License is distributed on an "AS IS" BASIS,
  37. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  38. * See the License for the specific language governing permissions and
  39. * limitations under the License.
  40. */
  41. class APIRequestContext extends _instrumentation.SdkObject {
  42. static findResponseBody(guid) {
  43. for (const request of APIRequestContext.allInstances) {
  44. const body = request.fetchResponses.get(guid);
  45. if (body) return body;
  46. }
  47. return undefined;
  48. }
  49. constructor(parent) {
  50. super(parent, 'request-context');
  51. this.fetchResponses = new Map();
  52. this.fetchLog = new Map();
  53. this._activeProgressControllers = new Set();
  54. this._closeReason = void 0;
  55. APIRequestContext.allInstances.add(this);
  56. }
  57. _disposeImpl() {
  58. APIRequestContext.allInstances.delete(this);
  59. this.fetchResponses.clear();
  60. this.fetchLog.clear();
  61. this.emit(APIRequestContext.Events.Dispose);
  62. }
  63. disposeResponse(fetchUid) {
  64. this.fetchResponses.delete(fetchUid);
  65. this.fetchLog.delete(fetchUid);
  66. }
  67. _storeResponseBody(body) {
  68. const uid = (0, _utils.createGuid)();
  69. this.fetchResponses.set(uid, body);
  70. return uid;
  71. }
  72. async fetch(params, metadata) {
  73. var _params$method;
  74. const defaults = this._defaultOptions();
  75. const headers = {
  76. 'user-agent': defaults.userAgent,
  77. 'accept': '*/*',
  78. 'accept-encoding': 'gzip,deflate,br'
  79. };
  80. if (defaults.extraHTTPHeaders) {
  81. for (const {
  82. name,
  83. value
  84. } of defaults.extraHTTPHeaders) setHeader(headers, name, value);
  85. }
  86. if (params.headers) {
  87. for (const {
  88. name,
  89. value
  90. } of params.headers) setHeader(headers, name, value);
  91. }
  92. const requestUrl = new URL(params.url, defaults.baseURL);
  93. if (params.params) {
  94. for (const {
  95. name,
  96. value
  97. } of params.params) requestUrl.searchParams.set(name, value);
  98. }
  99. const method = ((_params$method = params.method) === null || _params$method === void 0 ? void 0 : _params$method.toUpperCase()) || 'GET';
  100. const proxy = defaults.proxy;
  101. let agent;
  102. if (proxy && proxy.server !== 'per-context' && !shouldBypassProxy(requestUrl, proxy.bypass)) {
  103. var _proxyOpts$protocol;
  104. const proxyOpts = _url.default.parse(proxy.server);
  105. if ((_proxyOpts$protocol = proxyOpts.protocol) !== null && _proxyOpts$protocol !== void 0 && _proxyOpts$protocol.startsWith('socks')) {
  106. agent = new _utilsBundle.SocksProxyAgent({
  107. host: proxyOpts.hostname,
  108. port: proxyOpts.port || undefined
  109. });
  110. } else {
  111. if (proxy.username) proxyOpts.auth = `${proxy.username}:${proxy.password || ''}`;
  112. // TODO: We should use HttpProxyAgent conditional on proxyOpts.protocol instead of always using CONNECT method.
  113. agent = new _utilsBundle.HttpsProxyAgent(proxyOpts);
  114. }
  115. }
  116. const timeout = defaults.timeoutSettings.timeout(params);
  117. const deadline = timeout && (0, _utils.monotonicTime)() + timeout;
  118. const options = {
  119. method,
  120. headers,
  121. agent,
  122. maxRedirects: params.maxRedirects === 0 ? -1 : params.maxRedirects === undefined ? 20 : params.maxRedirects,
  123. timeout,
  124. deadline,
  125. __testHookLookup: params.__testHookLookup
  126. };
  127. // rejectUnauthorized = undefined is treated as true in node 12.
  128. if (params.ignoreHTTPSErrors || defaults.ignoreHTTPSErrors) options.rejectUnauthorized = false;
  129. const postData = serializePostData(params, headers);
  130. if (postData) setHeader(headers, 'content-length', String(postData.byteLength));
  131. const controller = new _progress.ProgressController(metadata, this);
  132. const fetchResponse = await controller.run(progress => {
  133. return this._sendRequest(progress, requestUrl, options, postData);
  134. });
  135. const fetchUid = this._storeResponseBody(fetchResponse.body);
  136. this.fetchLog.set(fetchUid, controller.metadata.log);
  137. if (params.failOnStatusCode && (fetchResponse.status < 200 || fetchResponse.status >= 400)) throw new Error(`${fetchResponse.status} ${fetchResponse.statusText}`);
  138. return {
  139. ...fetchResponse,
  140. fetchUid
  141. };
  142. }
  143. _parseSetCookieHeader(responseUrl, setCookie) {
  144. if (!setCookie) return [];
  145. const url = new URL(responseUrl);
  146. // https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.4
  147. const defaultPath = '/' + url.pathname.substr(1).split('/').slice(0, -1).join('/');
  148. const cookies = [];
  149. for (const header of setCookie) {
  150. // Decode cookie value?
  151. const cookie = parseCookie(header);
  152. if (!cookie) continue;
  153. // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.3
  154. if (!cookie.domain) cookie.domain = url.hostname;else (0, _utils.assert)(cookie.domain.startsWith('.') || !cookie.domain.includes('.'));
  155. if (!(0, _cookieStore.domainMatches)(url.hostname, cookie.domain)) continue;
  156. // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.4
  157. if (!cookie.path || !cookie.path.startsWith('/')) cookie.path = defaultPath;
  158. cookies.push(cookie);
  159. }
  160. return cookies;
  161. }
  162. async _updateRequestCookieHeader(url, headers) {
  163. if (getHeader(headers, 'cookie') !== undefined) return;
  164. const cookies = await this._cookies(url);
  165. if (cookies.length) {
  166. const valueArray = cookies.map(c => `${c.name}=${c.value}`);
  167. setHeader(headers, 'cookie', valueArray.join('; '));
  168. }
  169. }
  170. async _sendRequest(progress, url, options, postData) {
  171. var _getHeader;
  172. await this._updateRequestCookieHeader(url, options.headers);
  173. const requestCookies = ((_getHeader = getHeader(options.headers, 'cookie')) === null || _getHeader === void 0 ? void 0 : _getHeader.split(';').map(p => {
  174. const [name, value] = p.split('=').map(v => v.trim());
  175. return {
  176. name,
  177. value
  178. };
  179. })) || [];
  180. const requestEvent = {
  181. url,
  182. method: options.method,
  183. headers: options.headers,
  184. cookies: requestCookies,
  185. postData
  186. };
  187. this.emit(APIRequestContext.Events.Request, requestEvent);
  188. return new Promise((fulfill, reject) => {
  189. const requestConstructor = (url.protocol === 'https:' ? https : http).request;
  190. // If we have a proxy agent already, do not override it.
  191. const agent = options.agent || (url.protocol === 'https:' ? _happyEyeballs.httpsHappyEyeballsAgent : _happyEyeballs.httpHappyEyeballsAgent);
  192. const requestOptions = {
  193. ...options,
  194. agent
  195. };
  196. const request = requestConstructor(url, requestOptions, async response => {
  197. const notifyRequestFinished = body => {
  198. const requestFinishedEvent = {
  199. requestEvent,
  200. httpVersion: response.httpVersion,
  201. statusCode: response.statusCode || 0,
  202. statusMessage: response.statusMessage || '',
  203. headers: response.headers,
  204. rawHeaders: response.rawHeaders,
  205. cookies,
  206. body
  207. };
  208. this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
  209. };
  210. progress.log(`← ${response.statusCode} ${response.statusMessage}`);
  211. for (const [name, value] of Object.entries(response.headers)) progress.log(` ${name}: ${value}`);
  212. const cookies = this._parseSetCookieHeader(response.url || url.toString(), response.headers['set-cookie']);
  213. if (cookies.length) {
  214. try {
  215. await this._addCookies(cookies);
  216. } catch (e) {
  217. // Cookie value is limited by 4096 characters in the browsers. If setCookies failed,
  218. // we try setting each cookie individually just in case only some of them are bad.
  219. await Promise.all(cookies.map(c => this._addCookies([c]).catch(() => {})));
  220. }
  221. }
  222. if (redirectStatus.includes(response.statusCode) && options.maxRedirects >= 0) {
  223. if (!options.maxRedirects) {
  224. reject(new Error('Max redirect count exceeded'));
  225. request.destroy();
  226. return;
  227. }
  228. const headers = {
  229. ...options.headers
  230. };
  231. removeHeader(headers, `cookie`);
  232. // HTTP-redirect fetch step 13 (https://fetch.spec.whatwg.org/#http-redirect-fetch)
  233. const status = response.statusCode;
  234. let method = options.method;
  235. if ((status === 301 || status === 302) && method === 'POST' || status === 303 && !['GET', 'HEAD'].includes(method)) {
  236. method = 'GET';
  237. postData = undefined;
  238. removeHeader(headers, `content-encoding`);
  239. removeHeader(headers, `content-language`);
  240. removeHeader(headers, `content-length`);
  241. removeHeader(headers, `content-location`);
  242. removeHeader(headers, `content-type`);
  243. }
  244. const redirectOptions = {
  245. method,
  246. headers,
  247. agent: options.agent,
  248. maxRedirects: options.maxRedirects - 1,
  249. timeout: options.timeout,
  250. deadline: options.deadline,
  251. __testHookLookup: options.__testHookLookup
  252. };
  253. // rejectUnauthorized = undefined is treated as true in node 12.
  254. if (options.rejectUnauthorized === false) redirectOptions.rejectUnauthorized = false;
  255. // HTTP-redirect fetch step 4: If locationURL is null, then return response.
  256. if (response.headers.location) {
  257. let locationURL;
  258. try {
  259. locationURL = new URL(response.headers.location, url);
  260. } catch (error) {
  261. reject(new Error(`uri requested responds with an invalid redirect URL: ${response.headers.location}`));
  262. request.destroy();
  263. return;
  264. }
  265. if (headers['host']) headers['host'] = locationURL.host;
  266. notifyRequestFinished();
  267. fulfill(this._sendRequest(progress, locationURL, redirectOptions, postData));
  268. request.destroy();
  269. return;
  270. }
  271. }
  272. if (response.statusCode === 401 && !getHeader(options.headers, 'authorization')) {
  273. const auth = response.headers['www-authenticate'];
  274. const credentials = this._getHttpCredentials(url);
  275. if (auth !== null && auth !== void 0 && auth.trim().startsWith('Basic') && credentials) {
  276. const {
  277. username,
  278. password
  279. } = credentials;
  280. const encoded = Buffer.from(`${username || ''}:${password || ''}`).toString('base64');
  281. setHeader(options.headers, 'authorization', `Basic ${encoded}`);
  282. notifyRequestFinished();
  283. fulfill(this._sendRequest(progress, url, options, postData));
  284. request.destroy();
  285. return;
  286. }
  287. }
  288. response.on('aborted', () => reject(new Error('aborted')));
  289. const chunks = [];
  290. const notifyBodyFinished = () => {
  291. const body = Buffer.concat(chunks);
  292. notifyRequestFinished(body);
  293. fulfill({
  294. url: response.url || url.toString(),
  295. status: response.statusCode || 0,
  296. statusText: response.statusMessage || '',
  297. headers: toHeadersArray(response.rawHeaders),
  298. body
  299. });
  300. };
  301. let body = response;
  302. let transform;
  303. const encoding = response.headers['content-encoding'];
  304. if (encoding === 'gzip' || encoding === 'x-gzip') {
  305. transform = _zlib.default.createGunzip({
  306. flush: _zlib.default.constants.Z_SYNC_FLUSH,
  307. finishFlush: _zlib.default.constants.Z_SYNC_FLUSH
  308. });
  309. } else if (encoding === 'br') {
  310. transform = _zlib.default.createBrotliDecompress();
  311. } else if (encoding === 'deflate') {
  312. transform = _zlib.default.createInflate();
  313. }
  314. if (transform) {
  315. // Brotli and deflate decompressors throw if the input stream is empty.
  316. const emptyStreamTransform = new SafeEmptyStreamTransform(notifyBodyFinished);
  317. body = (0, _stream.pipeline)(response, emptyStreamTransform, transform, e => {
  318. if (e) reject(new Error(`failed to decompress '${encoding}' encoding: ${e.message}`));
  319. });
  320. body.on('error', e => reject(new Error(`failed to decompress '${encoding}' encoding: ${e}`)));
  321. } else {
  322. body.on('error', reject);
  323. }
  324. body.on('data', chunk => chunks.push(chunk));
  325. body.on('end', notifyBodyFinished);
  326. });
  327. request.on('error', reject);
  328. const disposeListener = () => {
  329. reject(new Error('Request context disposed.'));
  330. request.destroy();
  331. };
  332. this.on(APIRequestContext.Events.Dispose, disposeListener);
  333. request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
  334. progress.log(`→ ${options.method} ${url.toString()}`);
  335. if (options.headers) {
  336. for (const [name, value] of Object.entries(options.headers)) progress.log(` ${name}: ${value}`);
  337. }
  338. if (options.deadline) {
  339. const rejectOnTimeout = () => {
  340. reject(new Error(`Request timed out after ${options.timeout}ms`));
  341. request.destroy();
  342. };
  343. const remaining = options.deadline - (0, _utils.monotonicTime)();
  344. if (remaining <= 0) {
  345. rejectOnTimeout();
  346. return;
  347. }
  348. request.setTimeout(remaining, rejectOnTimeout);
  349. }
  350. if (postData) request.write(postData);
  351. request.end();
  352. });
  353. }
  354. _getHttpCredentials(url) {
  355. var _this$_defaultOptions, _this$_defaultOptions2, _this$_defaultOptions3;
  356. if (!((_this$_defaultOptions = this._defaultOptions().httpCredentials) !== null && _this$_defaultOptions !== void 0 && _this$_defaultOptions.origin) || url.origin.toLowerCase() === ((_this$_defaultOptions2 = this._defaultOptions().httpCredentials) === null || _this$_defaultOptions2 === void 0 ? void 0 : (_this$_defaultOptions3 = _this$_defaultOptions2.origin) === null || _this$_defaultOptions3 === void 0 ? void 0 : _this$_defaultOptions3.toLowerCase())) return this._defaultOptions().httpCredentials;
  357. return undefined;
  358. }
  359. }
  360. exports.APIRequestContext = APIRequestContext;
  361. APIRequestContext.Events = {
  362. Dispose: 'dispose',
  363. Request: 'request',
  364. RequestFinished: 'requestfinished'
  365. };
  366. APIRequestContext.allInstances = new Set();
  367. class SafeEmptyStreamTransform extends _stream.Transform {
  368. constructor(onEmptyStreamCallback) {
  369. super();
  370. this._receivedSomeData = false;
  371. this._onEmptyStreamCallback = void 0;
  372. this._onEmptyStreamCallback = onEmptyStreamCallback;
  373. }
  374. _transform(chunk, encoding, callback) {
  375. this._receivedSomeData = true;
  376. callback(null, chunk);
  377. }
  378. _flush(callback) {
  379. if (this._receivedSomeData) callback(null);else this._onEmptyStreamCallback();
  380. }
  381. }
  382. class BrowserContextAPIRequestContext extends APIRequestContext {
  383. constructor(context) {
  384. super(context);
  385. this._context = void 0;
  386. this._context = context;
  387. context.once(_browserContext.BrowserContext.Events.Close, () => this._disposeImpl());
  388. }
  389. tracing() {
  390. return this._context.tracing;
  391. }
  392. async dispose() {
  393. this.fetchResponses.clear();
  394. }
  395. _defaultOptions() {
  396. return {
  397. userAgent: this._context._options.userAgent || this._context._browser.userAgent(),
  398. extraHTTPHeaders: this._context._options.extraHTTPHeaders,
  399. httpCredentials: this._context._options.httpCredentials,
  400. proxy: this._context._options.proxy || this._context._browser.options.proxy,
  401. timeoutSettings: this._context._timeoutSettings,
  402. ignoreHTTPSErrors: this._context._options.ignoreHTTPSErrors,
  403. baseURL: this._context._options.baseURL
  404. };
  405. }
  406. async _addCookies(cookies) {
  407. await this._context.addCookies(cookies);
  408. }
  409. async _cookies(url) {
  410. return await this._context.cookies(url.toString());
  411. }
  412. async storageState() {
  413. return this._context.storageState();
  414. }
  415. }
  416. exports.BrowserContextAPIRequestContext = BrowserContextAPIRequestContext;
  417. class GlobalAPIRequestContext extends APIRequestContext {
  418. constructor(playwright, options) {
  419. super(playwright);
  420. this._cookieStore = new _cookieStore.CookieStore();
  421. this._options = void 0;
  422. this._origins = void 0;
  423. this._tracing = void 0;
  424. this.attribution.context = this;
  425. const timeoutSettings = new _timeoutSettings.TimeoutSettings();
  426. if (options.timeout !== undefined) timeoutSettings.setDefaultTimeout(options.timeout);
  427. const proxy = options.proxy;
  428. if (proxy !== null && proxy !== void 0 && proxy.server) {
  429. let url = proxy === null || proxy === void 0 ? void 0 : proxy.server.trim();
  430. if (!/^\w+:\/\//.test(url)) url = 'http://' + url;
  431. proxy.server = url;
  432. }
  433. if (options.storageState) {
  434. this._origins = options.storageState.origins;
  435. this._cookieStore.addCookies(options.storageState.cookies || []);
  436. }
  437. this._options = {
  438. baseURL: options.baseURL,
  439. userAgent: options.userAgent || (0, _userAgent.getUserAgent)(),
  440. extraHTTPHeaders: options.extraHTTPHeaders,
  441. ignoreHTTPSErrors: !!options.ignoreHTTPSErrors,
  442. httpCredentials: options.httpCredentials,
  443. proxy,
  444. timeoutSettings
  445. };
  446. this._tracing = new _tracing.Tracing(this, options.tracesDir);
  447. }
  448. tracing() {
  449. return this._tracing;
  450. }
  451. async dispose() {
  452. await this._tracing.flush();
  453. await this._tracing.deleteTmpTracesDir();
  454. this._disposeImpl();
  455. }
  456. _defaultOptions() {
  457. return this._options;
  458. }
  459. async _addCookies(cookies) {
  460. this._cookieStore.addCookies(cookies);
  461. }
  462. async _cookies(url) {
  463. return this._cookieStore.cookies(url);
  464. }
  465. async storageState() {
  466. return {
  467. cookies: this._cookieStore.allCookies(),
  468. origins: this._origins || []
  469. };
  470. }
  471. }
  472. exports.GlobalAPIRequestContext = GlobalAPIRequestContext;
  473. function toHeadersArray(rawHeaders) {
  474. const result = [];
  475. for (let i = 0; i < rawHeaders.length; i += 2) result.push({
  476. name: rawHeaders[i],
  477. value: rawHeaders[i + 1]
  478. });
  479. return result;
  480. }
  481. const redirectStatus = [301, 302, 303, 307, 308];
  482. function parseCookie(header) {
  483. const pairs = header.split(';').filter(s => s.trim().length > 0).map(p => {
  484. let key = '';
  485. let value = '';
  486. const separatorPos = p.indexOf('=');
  487. if (separatorPos === -1) {
  488. // If only a key is specified, the value is left undefined.
  489. key = p.trim();
  490. } else {
  491. // Otherwise we assume that the key is the element before the first `=`
  492. key = p.slice(0, separatorPos).trim();
  493. // And the value is the rest of the string.
  494. value = p.slice(separatorPos + 1).trim();
  495. }
  496. return [key, value];
  497. });
  498. if (!pairs.length) return null;
  499. const [name, value] = pairs[0];
  500. const cookie = {
  501. name,
  502. value,
  503. domain: '',
  504. path: '',
  505. expires: -1,
  506. httpOnly: false,
  507. secure: false,
  508. // From https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
  509. // The cookie-sending behavior if SameSite is not specified is SameSite=Lax.
  510. sameSite: 'Lax'
  511. };
  512. for (let i = 1; i < pairs.length; i++) {
  513. const [name, value] = pairs[i];
  514. switch (name.toLowerCase()) {
  515. case 'expires':
  516. const expiresMs = +new Date(value);
  517. // https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.1
  518. if (isFinite(expiresMs)) {
  519. if (expiresMs <= 0) cookie.expires = 0;else cookie.expires = Math.min(expiresMs / 1000, _network.kMaxCookieExpiresDateInSeconds);
  520. }
  521. break;
  522. case 'max-age':
  523. const maxAgeSec = parseInt(value, 10);
  524. if (isFinite(maxAgeSec)) {
  525. // From https://datatracker.ietf.org/doc/html/rfc6265#section-5.2.2
  526. // If delta-seconds is less than or equal to zero (0), let expiry-time
  527. // be the earliest representable date and time.
  528. if (maxAgeSec <= 0) cookie.expires = 0;else cookie.expires = Math.min(Date.now() / 1000 + maxAgeSec, _network.kMaxCookieExpiresDateInSeconds);
  529. }
  530. break;
  531. case 'domain':
  532. cookie.domain = value.toLocaleLowerCase() || '';
  533. if (cookie.domain && !cookie.domain.startsWith('.') && cookie.domain.includes('.')) cookie.domain = '.' + cookie.domain;
  534. break;
  535. case 'path':
  536. cookie.path = value || '';
  537. break;
  538. case 'secure':
  539. cookie.secure = true;
  540. break;
  541. case 'httponly':
  542. cookie.httpOnly = true;
  543. break;
  544. case 'samesite':
  545. switch (value.toLowerCase()) {
  546. case 'none':
  547. cookie.sameSite = 'None';
  548. break;
  549. case 'lax':
  550. cookie.sameSite = 'Lax';
  551. break;
  552. case 'strict':
  553. cookie.sameSite = 'Strict';
  554. break;
  555. }
  556. break;
  557. }
  558. }
  559. return cookie;
  560. }
  561. function serializePostData(params, headers) {
  562. (0, _utils.assert)((params.postData ? 1 : 0) + (params.jsonData ? 1 : 0) + (params.formData ? 1 : 0) + (params.multipartData ? 1 : 0) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
  563. if (params.jsonData !== undefined) {
  564. setHeader(headers, 'content-type', 'application/json', true);
  565. return Buffer.from(params.jsonData, 'utf8');
  566. } else if (params.formData) {
  567. const searchParams = new URLSearchParams();
  568. for (const {
  569. name,
  570. value
  571. } of params.formData) searchParams.append(name, value);
  572. setHeader(headers, 'content-type', 'application/x-www-form-urlencoded', true);
  573. return Buffer.from(searchParams.toString(), 'utf8');
  574. } else if (params.multipartData) {
  575. const formData = new _formData.MultipartFormData();
  576. for (const field of params.multipartData) {
  577. if (field.file) formData.addFileField(field.name, field.file);else if (field.value) formData.addField(field.name, field.value);
  578. }
  579. setHeader(headers, 'content-type', formData.contentTypeHeader(), true);
  580. return formData.finish();
  581. } else if (params.postData !== undefined) {
  582. setHeader(headers, 'content-type', 'application/octet-stream', true);
  583. return params.postData;
  584. }
  585. return undefined;
  586. }
  587. function setHeader(headers, name, value, keepExisting = false) {
  588. const existing = Object.entries(headers).find(pair => pair[0].toLowerCase() === name.toLowerCase());
  589. if (!existing) headers[name] = value;else if (!keepExisting) headers[existing[0]] = value;
  590. }
  591. function getHeader(headers, name) {
  592. const existing = Object.entries(headers).find(pair => pair[0].toLowerCase() === name.toLowerCase());
  593. return existing ? existing[1] : undefined;
  594. }
  595. function removeHeader(headers, name) {
  596. delete headers[name];
  597. }
  598. function shouldBypassProxy(url, bypass) {
  599. if (!bypass) return false;
  600. const domains = bypass.split(',').map(s => {
  601. s = s.trim();
  602. if (!s.startsWith('.')) s = '.' + s;
  603. return s;
  604. });
  605. const domain = '.' + url.hostname;
  606. return domains.some(d => domain.endsWith(d));
  607. }