From f0c2e6cfafdef3d2f5ea9bec6b4b954e574bcc12 Mon Sep 17 00:00:00 2001 From: rutan Date: Wed, 2 Oct 2024 01:06:50 +0900 Subject: [PATCH] fix taint canvas --- src/functions/pinp.ts | 47 ++++++++++++++++++++++++++----------------- src/inject.ts | 17 +++++++++++++--- 2 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/functions/pinp.ts b/src/functions/pinp.ts index bb2fe66..88d3ce0 100644 --- a/src/functions/pinp.ts +++ b/src/functions/pinp.ts @@ -77,9 +77,11 @@ export function handleVideo(): void { const supporterCanvas = document.querySelector( SUPPORTER_VIEW_CANVAS_SELECTOR, ); - const akashicCanvas = document.querySelector( + + let akashicCanvas = document.querySelector( AKASHIC_CANVAS_SELECTOR, ); + const akashicCanvasContext = akashicCanvas?.getContext('2d'); function update() { if (!comment || !targetVideo || !context || myId !== uid) { @@ -149,23 +151,32 @@ export function handleVideo(): void { // akashic if (akashicCanvas) { - const size = calcSize( - akashicCanvas.width, - akashicCanvas.height, - canvas.width, - canvas.height, - ); - context.drawImage( - akashicCanvas, - 0, - 0, - akashicCanvas.width, - akashicCanvas.height, - (canvas.width - size.width) / 2, - (canvas.height - size.height) / 2, - size.width, - size.height, - ); + try { + // 外部リソースの読み込みによって canvas が汚染されていないかを確認 + akashicCanvasContext?.getImageData(0, 0, 1, 1); + + const size = calcSize( + akashicCanvas.width, + akashicCanvas.height, + canvas.width, + canvas.height, + ); + context.drawImage( + akashicCanvas, + 0, + 0, + akashicCanvas.width, + akashicCanvas.height, + (canvas.width - size.width) / 2, + (canvas.height - size.height) / 2, + size.width, + size.height, + ); + } catch (e) { + // canvas が汚染されたいる場合は AkashicCanvas をPinP対象から削除 + console.error(e); + akashicCanvas = null; + } } // comment diff --git a/src/inject.ts b/src/inject.ts index d487123..b76ee9c 100644 --- a/src/inject.ts +++ b/src/inject.ts @@ -1,5 +1,9 @@ +// ニコ生のギフト描画用 +// AkashicのCanvasが汚染されることを防ぐため、必要に応じて自動的に corssOrigin="anonymous" を付与する (() => { - const cdnNimgJpPattern = /^https:\/\/[^.]+\.cdn\.nimg\.jp\//; + const resourceAkashicCoePrefix = 'https://resource.akashic.coe.nicovideo.jp/'; + const irCdnNimgJpPrefix = 'https://ir.cdn.nimg.jp/'; + const secureDcdnPrefix = 'https://secure-dcdn.cdn.nimg.jp/'; class AutoAnonymousImage extends Image { get src() { @@ -8,13 +12,20 @@ set src(value: string) { super.src = value; - if (value?.match(cdnNimgJpPattern)) { + if ( + // Akashic + value?.startsWith(resourceAkashicCoePrefix) || + // ギフト + value?.startsWith(irCdnNimgJpPrefix) || + // エモーション + value?.startsWith(secureDcdnPrefix) + ) { this.crossOrigin = 'anonymous'; } } } window.Image = new Proxy(window.Image, { - construct: (target, args) => new AutoAnonymousImage(...args), + construct: (_target, args) => new AutoAnonymousImage(...args), }); })();