patchright-core 1.55.2 → 1.55.3

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.
@@ -73,7 +73,6 @@ const chromiumSwitches = (assistantMode, channel) => [
73
73
  "--disable-search-engine-choice-screen",
74
74
  // Edge can potentially restart on Windows (msRelaunchNoCompatLayer) which looses its file descriptors (stdout/stderr) and CDP (3/4). Disable until fixed upstream.
75
75
  "--edge-skip-compat-layer-relaunch",
76
- assistantMode ? "" : "--enable-automation",
77
76
  "--disable-blink-features=AutomationControlled"
78
77
  ].filter(Boolean);
79
78
  // Annotate the CommonJS export names for ESM import in node:
@@ -533,57 +533,91 @@ class RouteImpl {
533
533
  if (!allInjections.includes(binding)) allInjections.push(binding);
534
534
  }
535
535
  if (isTextHtml && allInjections.length) {
536
- let scriptNonce = import_crypto.default.randomBytes(22).toString("hex");
537
- let useNonce = true;
536
+ let useNonce = false;
537
+ let scriptNonce = null;
538
+ if (response.isBase64) {
539
+ response.isBase64 = false;
540
+ response.body = Buffer.from(response.body, "base64").toString("utf-8");
541
+ }
542
+ const cspHeaderNames = ["content-security-policy", "content-security-policy-report-only"];
538
543
  for (let i = 0; i < response.headers.length; i++) {
539
- if (response.headers[i].name.toLowerCase() === "content-security-policy" || response.headers[i].name.toLowerCase() === "content-security-policy-report-only") {
540
- let cspValue = response.headers[i].value;
541
- const nonceRegex = /script-src[^;]*'nonce-([\w-]+)'/;
542
- const nonceMatch = cspValue.match(nonceRegex);
543
- if (nonceMatch) {
544
- scriptNonce = nonceMatch[1];
545
- } else {
546
- if (/script-src[^;]*'unsafe-inline'/.test(cspValue)) {
547
- useNonce = false;
548
- } else {
549
- const scriptSrcRegex = /(script-src[^;]*)(;|$)/;
550
- const newCspValue = cspValue.replace(scriptSrcRegex, `$1 'nonce-${scriptNonce}'$2`);
551
- response.headers[i].value = newCspValue;
544
+ const headerName = response.headers[i].name.toLowerCase();
545
+ if (cspHeaderNames.includes(headerName)) {
546
+ const originalCsp = response.headers[i].value || "";
547
+ if (!useNonce) {
548
+ const nonceMatch = originalCsp.match(/script-src[^;]*'nonce-([^'"\s;]+)'/i);
549
+ if (nonceMatch && nonceMatch[1]) {
550
+ scriptNonce = nonceMatch[1];
551
+ useNonce = true;
552
552
  }
553
553
  }
554
- break;
554
+ const fixedCsp = this._fixCSP(originalCsp, scriptNonce);
555
+ response.headers[i].value = fixedCsp;
555
556
  }
556
557
  }
558
+ if (typeof response.body === "string" && response.body.length) {
559
+ response.body = response.body.replace(
560
+ /<meta[^>]*http-equiv=(?:"|')?Content-Security-Policy(?:"|')?[^>]*>/gi,
561
+ (match) => {
562
+ const contentMatch = match.match(/content=(?:"|')([^"']*)(?:"|')/i);
563
+ if (contentMatch && contentMatch[1]) {
564
+ let originalCsp = contentMatch[1];
565
+ originalCsp = originalCsp.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;/g, "'").replace(/&#x22;/g, '"').replace(/&nbsp;/g, " ").replace(/&#(d+);/g, (match2, dec) => String.fromCharCode(dec)).replace(/&#x([0-9a-fA-F]+);/g, (match2, hex) => String.fromCharCode(parseInt(hex, 16)));
566
+ if (!useNonce) {
567
+ const nonceMatch = originalCsp.match(/script-src[^;]*'nonce-([^'"\s;]+)'/i);
568
+ if (nonceMatch && nonceMatch[1]) {
569
+ scriptNonce = nonceMatch[1];
570
+ useNonce = true;
571
+ }
572
+ }
573
+ const fixedCsp = this._fixCSP(originalCsp, scriptNonce);
574
+ const encodedCsp = fixedCsp.replace(/'/g, "&#x27;").replace(/"/g, "&#x22;");
575
+ return match.replace(contentMatch[1], encodedCsp);
576
+ }
577
+ return match;
578
+ }
579
+ );
580
+ }
557
581
  let injectionHTML = "";
558
582
  allInjections.forEach((script) => {
559
583
  let scriptId = import_crypto.default.randomBytes(22).toString("hex");
560
584
  let scriptSource = script.source || script;
561
- if (useNonce) {
562
- injectionHTML += `<script class="${this._page.delegate.initScriptTag}" nonce="${scriptNonce}" id="${scriptId}" type="text/javascript">document.getElementById("${scriptId}")?.remove();${scriptSource}</script>`;
563
- } else {
564
- injectionHTML += `<script class="${this._page.delegate.initScriptTag}" id="${scriptId}" type="text/javascript">document.getElementById("${scriptId}")?.remove();${scriptSource}</script>`;
565
- }
585
+ const nonceAttr = useNonce ? `nonce="${scriptNonce}"` : "";
586
+ injectionHTML += `<script class="${this._page.delegate.initScriptTag}" ${nonceAttr} id="${scriptId}" type="text/javascript">document.getElementById("${scriptId}")?.remove();${scriptSource}</script>`;
566
587
  });
567
- if (response.isBase64) {
568
- response.isBase64 = false;
569
- response.body = Buffer.from(response.body, "base64").toString("utf-8");
570
- }
571
- const headMatch = response.body.match(/<head[^>]*>[\s\S]*?<\/head>/i);
572
- if (headMatch) {
573
- response.body = response.body.replace(/(<head[^>]*>)([\s\S]*?)(<\/head>)/i, (match, headOpen, headContent, headClose) => {
574
- const scriptMatch = headContent.match(/([\s\S]*?)(<script[\s\S]*?$)/i);
575
- if (scriptMatch) {
576
- const [beforeScript, fromScript] = [scriptMatch[1], scriptMatch[2]];
577
- return `${headOpen}${beforeScript}${injectionHTML}${fromScript}${headClose}`;
588
+ const lower = response.body.toLowerCase();
589
+ const headStartIndex = lower.indexOf("<head");
590
+ if (headStartIndex !== -1) {
591
+ const headEndTagIndex = lower.indexOf("</head>", headStartIndex);
592
+ if (headEndTagIndex !== -1) {
593
+ const headOpenEnd = response.body.indexOf(">", headStartIndex) + 1;
594
+ const headContent = response.body.slice(headOpenEnd, headEndTagIndex);
595
+ const headContentLower = headContent.toLowerCase();
596
+ const firstScriptIndex = headContentLower.indexOf("<script");
597
+ if (firstScriptIndex !== -1) {
598
+ const insertPosition = headOpenEnd + firstScriptIndex;
599
+ response.body = response.body.slice(0, insertPosition) + injectionHTML + response.body.slice(insertPosition);
600
+ } else {
601
+ response.body = response.body.slice(0, headEndTagIndex) + injectionHTML + response.body.slice(headEndTagIndex);
578
602
  }
579
- return `${headOpen}${headContent}${injectionHTML}${headClose}`;
580
- });
581
- } else if (/^<!DOCTYPE[\s\S]*?>/i.test(response.body)) {
582
- response.body = response.body.replace(/^<!DOCTYPE[\s\S]*?>/i, (match) => `${match}${injectionHTML}`);
583
- } else if (/<html[^>]*>/i.test(response.body)) {
584
- response.body = response.body.replace(/<html[^>]*>/i, `$&<head>${injectionHTML}</head>`);
603
+ } else {
604
+ const headStartTagEnd = response.body.indexOf(">", headStartIndex) + 1;
605
+ response.body = response.body.slice(0, headStartTagEnd) + injectionHTML + response.body.slice(headStartTagEnd);
606
+ }
585
607
  } else {
586
- response.body = injectionHTML + response.body;
608
+ const doctypeIndex = lower.indexOf("<!doctype");
609
+ if (doctypeIndex === 0) {
610
+ const doctypeEnd = response.body.indexOf(">", doctypeIndex) + 1;
611
+ response.body = response.body.slice(0, doctypeEnd) + injectionHTML + response.body.slice(doctypeEnd);
612
+ } else {
613
+ const htmlIndex = lower.indexOf("<html");
614
+ if (htmlIndex !== -1) {
615
+ const htmlTagEnd = response.body.indexOf(">", htmlIndex) + 1;
616
+ response.body = response.body.slice(0, htmlTagEnd) + `<head>${injectionHTML}</head>` + response.body.slice(htmlTagEnd);
617
+ } else {
618
+ response.body = injectionHTML + response.body;
619
+ }
620
+ }
587
621
  }
588
622
  }
589
623
  this._fulfilled = true;
@@ -609,6 +643,79 @@ class RouteImpl {
609
643
  });
610
644
  });
611
645
  }
646
+ _fixCSP(csp, scriptNonce) {
647
+ if (!csp || typeof csp !== "string") return csp;
648
+ const directives = csp.split(";").map((d) => d.trim()).filter((d) => d && d.length > 0);
649
+ const fixedDirectives = [];
650
+ let hasScriptSrc = false;
651
+ for (let directive of directives) {
652
+ if (!directive.trim()) continue;
653
+ const parts = directive.trim().split(/s+/);
654
+ if (parts.length === 0) continue;
655
+ const directiveName = parts[0].toLowerCase();
656
+ const directiveValues = parts.slice(1);
657
+ switch (directiveName) {
658
+ case "script-src":
659
+ hasScriptSrc = true;
660
+ let values = [...directiveValues];
661
+ if (scriptNonce && !values.some((v) => v.includes(`nonce-${scriptNonce}`))) {
662
+ values.push(`'nonce-${scriptNonce}'`);
663
+ }
664
+ if (!values.includes("'unsafe-eval'")) {
665
+ values.push("'unsafe-eval'");
666
+ }
667
+ fixedDirectives.push(`script-src ${values.join(" ")}`);
668
+ break;
669
+ case "style-src":
670
+ let styleValues = [...directiveValues];
671
+ if (!styleValues.includes("'unsafe-inline'")) {
672
+ styleValues.push("'unsafe-inline'");
673
+ }
674
+ fixedDirectives.push(`style-src ${styleValues.join(" ")}`);
675
+ break;
676
+ case "img-src":
677
+ let imgValues = [...directiveValues];
678
+ if (!imgValues.includes("data:") && !imgValues.includes("*")) {
679
+ imgValues.push("data:");
680
+ }
681
+ fixedDirectives.push(`img-src ${imgValues.join(" ")}`);
682
+ break;
683
+ case "font-src":
684
+ let fontValues = [...directiveValues];
685
+ if (!fontValues.includes("data:") && !fontValues.includes("*")) {
686
+ fontValues.push("data:");
687
+ }
688
+ fixedDirectives.push(`font-src ${fontValues.join(" ")}`);
689
+ break;
690
+ case "connect-src":
691
+ let connectValues = [...directiveValues];
692
+ const hasWs = connectValues.some((v) => v.includes("ws:") || v.includes("wss:") || v === "*");
693
+ if (!hasWs) {
694
+ connectValues.push("ws:", "wss:");
695
+ }
696
+ fixedDirectives.push(`connect-src ${connectValues.join(" ")}`);
697
+ break;
698
+ case "frame-ancestors":
699
+ let frameAncestorValues = [...directiveValues];
700
+ if (frameAncestorValues.includes("'none'")) {
701
+ frameAncestorValues = ["'self'"];
702
+ }
703
+ fixedDirectives.push(`frame-ancestors ${frameAncestorValues.join(" ")}`);
704
+ break;
705
+ default:
706
+ fixedDirectives.push(directive);
707
+ break;
708
+ }
709
+ }
710
+ if (!hasScriptSrc) {
711
+ if (scriptNonce) {
712
+ fixedDirectives.push(`script-src 'self' 'unsafe-eval' 'nonce-${scriptNonce}'`);
713
+ } else {
714
+ fixedDirectives.push(`script-src 'self' 'unsafe-eval'`);
715
+ }
716
+ }
717
+ return fixedDirectives.join("; ");
718
+ }
612
719
  async _networkRequestIntercepted(event) {
613
720
  if (event.resourceType !== "Document") {
614
721
  return;