LoginSignup
13
3

More than 3 years have passed since last update.

CORSを使わずに別のサイトのHTMLファイルが読めないか試行錯誤して完全敗北した記録

Last updated at Posted at 2020-04-20

Overview

セキュリティ上の観点から、通常別のサイトからのデータは取得できない。
しかし、許可されている別のサイトならデータを取得を可能にするCORSという仕組みがある。
これのおかげでセキュアながら、連携したい部分とは連携できるようになっている。
しかし、全く関係のないサイトにはCORSの設定ができないため、データが取得できないという厳しい制約がある。

いろんなサイトのOGP画像をサーバーを介さずに取得してプレビューを作ろうと考えた。
HTMLファイルの取得で以下のよく見かけるエラーに遭遇。

Access to fetch at '<another site url>' from origin 'http://localhost:3000' has been blocked by CORS policy: 
No 'Access-Control-Allow-Origin' header is present on the requested resource.

HTMLファイルだけならどうにか取得する方法あるんじゃないか?
そもそもどこかに穴があれば狙われるからまず考えられないが、テキストとして取れないか?という希望があると思わないとどうにかなりそうで:sob:
半分は敗北の未来をわかっていながら、もしかしたら?と浅はかながらもいくつか試したことを残しておく。

結論としてはタイトル通り取得できなかったので、この先に何一つ希望はないということは断っておきたい。

Target reader

  • CORSを使わずに何を試したのか興味のある方。

Prerequisite

  • CORSが何かを理解している方。
  • Reactアプリに実装しているため、コードはすべてReactのコードの抜粋になります。
  • ブラウザはWindowsのChromeを使いました。

Body

Fetch API

CORSのことが完全に頭になかった時に普通に別サイトのHTMLを取得しようとしたコード。
fetch()で冒頭に掲載したAccess-Control-Allow-Originがないと怒られる。
XMLHttpRequestについてはMDNにfetch()と同様と読み取れたため試していない。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS

index.js

const fetchHtml = async (url) => {
  const response = await fetch(url);
  if (!response.ok) {
    const description = `status code:${response.status} , text:${response.statusText}`;
    throw new Error(description);
  }
  return await response.text();
}

iframe

TwitterやGoogle Mapや広告等はiframeで自分のサイトに埋め込むことができる。
実際デベロッパーツールではiframe内のDOMにアクセスできるため、これならいけるかも?とiframeに表示してそこにアクセスできないか試してみた。

index.js

const onLoadIframe = () => {
  const iframe = document.querySelector("#inlineFrameExample");
  const iframeDocument = iframe.contentWindow.document;
  const a = iframeDocument.querySelector("a");
  console.log("a:", a);
}

onLoadIframeiframeonloadイベントに接続して実施。
結果としては、HTMLは表示されるもののiframeDocumentの行でSecurityErrorなるものが発生しアクセス不可。
デベロッパーツールならみれるのにコードからは見れないのか:pensive:

object

iframeの派生形でobjectタグを使ってテキストとして読み込ませたらどうだろうか?
残念ながらtext/plainでテキスト指定しても、丁寧にHTMLとして読み込んでiframeと表示は変化なし。
結果としてもiframe同様SecurityErrorなるものが発生しアクセス不可。

index.js
            <object id="inlineFrameExample"
              width="100%"
              height="100%"
              data="https://sample.com/"
              type="text/plain"
              onLoad={handleiFrameonLoad}
            >
            </object>

winow.open()

iframeはダメだったが、winow.open()はどうだろうか?
半分駄目だろうなと思っていたが、やることに意味があると思う:laughing:
winow.open()ってこんなやつだったなぁ~と懐かしみつつコードを書いてみた。
しかし、情けないことにaddEventListeneronloadでFunctionを着火させられなかったため、window.open()の5秒後にquerySelector()を実行するコードになっている:joy:

index.js

const openWindow = (url) => {
  const w = window.open("");
  // w.addEventListener("load", (event) => {
  //   const a = w.document.querySelector("a");
  //   console.log("a:", a);
  // })

  // w.onload = (event) => {
  //   const a = w.document.querySelector("a");
  //   console.log("a:", a);
  // }

  w.location = url;

  setTimeout(() => {
    const a = w.document.querySelector("a");
    console.log("a:", a);
  }, 5000)
}

結論としてはw.document.querySelector("a")が動作したところで、iframe同様SecurityErrorなるものが発生しアクセス不可。
これは想定していた…うん…

Web Worker

色々調べていたらWeb Workerなるものが結構前からいることを知る!
https://developer.mozilla.org/ja/docs/Web/API/Web_Workers_API/Using_web_workers
Service Workerしか知らなかったので、ブラウザで以前から複数スレッド使えるのには驚いた。
サーバーサイドでは普通に取得できるし、それを受け取るのも問題ないから、DOMも扱えないバックグラウンドで動くこいつならCORS無視できるのかも?という淡い期待でトライしてみる。

基本的にはこのコードを拝借させていただいた。
https://github.com/facebook/create-react-app/issues/1277#issuecomment-345516463
MyWorkerの部分がエラーになったのでエラー箇所だけ修正した。

webWorker.js
export default class WebWorker {
    constructor(worker) {
        let code = worker.toString();
        code = code.substring(code.indexOf("{") + 1, code.lastIndexOf("}"));

        // console.log("code:", code)

        const blob = new Blob([code], { type: "application/javascript" });
        return new Worker(URL.createObjectURL(blob));
    }
}
myWorker.js
export default function MyWorker(args) {
    onmessage = (e) => {
        const { url } = e.data;

        fetch(url)
            .then(response => response.text())
            .then(html => console.log(html));

        postMessage("Response");
    };
}
index.js
import WebWorker from './utils/webworker';
import MyWorker from './utils/myWorker';

const fetchHtml = async (url) => {
  // Worker initialisation
  const workerInstance = new WebWorker(MyWorker);

  // Communication with worker
  workerInstance.addEventListener("message", e => console.log(e.data), false);
  workerInstance.postMessage({url});
}

注意したいのがmyWorker.js
fetch()の部分はawaitしたくなるが、onmessageasyncを付与するとnew Workerでエラーが発生する。
webWorker.jscodeの出力を見たが、asyncの影響で出力されたコードが結構変わるのでそこが影響しているのかもしれない。

awaitはあきらめてthen()でつないでいるが、postMessage("Response")をHTMLを取得後に出すならthen()でつなごう:sweat_smile:

実行結果としてはFetch API同様Access-Control-Allow-Originがないエラーだった気がする。

リダイレクト

視点を変えてCORSのプリフライト後に別サイトにリダイレクトしたらもしかしたら行けないだろうか?と思いつく。
リダイレクトは許可していない旨をMDNで見たような気もするが、この際やれることはやろう。
ソースコードはCloud Functions(サーバーサイド)のコード。
プリプライトは通常通り許可し、本番のGETできたときに302応答でフロントエンドが取得要求しているurlにリダイレクトさせる。

index.js

exports.fetchHtml = async (req, res) => {
    res.set('Access-Control-Allow-Origin', '*');

    if (req.method === 'OPTIONS') {
        // Send response to OPTIONS requests
        res.set('Access-Control-Allow-Methods', 'GET,POST');
        res.set('Access-Control-Allow-Headers', 'Content-Type');
        res.set('Access-Control-Max-Age', '-1'); // disable cache
        res.status(204).send('');
    } else {
        res.redirect(302, res.body.url);
    }
};

結果としてはFetch APIと同様で、リダイレクトの効果はなかった。

WebAssembly

執筆後にWebAssemblyならもしかしてCORSが必要ないとかいうことないだろうか?と思いつく。
簡単に調べてみたところ、BlazorでもCORSが必要とドキュメントにあるためトライせずに終了:expressionless:

Blazor WebAssembly サンプル アプリ (BlazorWebAssemblySample) は、呼び出し Web API コンポーネント (Pages/CallWebAPI.razor) で CORS を使用する具体的な方法を示しています。

Conclusion

比較的簡単に思いつく方法で試してみましたが全てNGで、結局はサーバーで取得してフロントに渡すしかないかなという結論です。
ブラウザで表示はできるし、デベロッパーツールでDOMにもアクセスできるけど、プログラムとしては取得不可…(゜-゜)
仮に方法があったとしても全てのブラウザで動作しないかもしれないし、いずれ塞がれるかもしれないと前途多難。
それでもなお挑戦する方の時間節約になればこれ幸い:joy:

モバイルアプリなら、モバイルアプリでテキスト取得してWebViewにデータ渡せそうな気がする(確証なし)…まあそれだけのためにモバイルアプリは作らないけど

Have a great day!

References

Create React AppのWeb Workerサポートのissue
https://github.com/facebook/create-react-app/issues/3660

13
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
3