こんにちは。
画像をくっつけるツールというjsで簡単な画像処理を行うSPAを作った時に、
URLを指定して画像を読み込んで結合する、という要件があり、
この要件とcanvas周りでハマったので対象方法を残します。
という流れで処理をしようと目論んでいたのですが、3でエラーが起きました。
Unable to get image data from canvas because the canvas has been tainted by cross-origin data.
上記の記事に解説がありますが、外部ドメインのデータによってcanvasが汚染されている、というエラーでした。
上記の記事のように、画像サーバでCORSをわざわざ有効にしてくれるパターンはごく稀で、大体の場合この制限に引っかります。
ということで、自分のサーバをプロキシとして扱い、
として、自分のサーバから返されたバイナリなので制限を突破する、という作戦で行きます。
もちろんセキュリティ上の制限を無理やり超えるので、ブラウザの脆弱性を突かれる可能性があります。ご留意の上ご使用下さい。
以下に具体的な手段を載せます。
今回の場合、セッションを持っておらず、URLパラメータに対しても反応しないので、悪意のあるURLを誰かに踏ませることは出来ないので自分で悪意のある入力をして自分に攻撃をすることしかできない。iframe埋め込みもさせないので、他人のPCへ攻撃はできない。という仮定で作っています。
もしレスポンスを受け取ってechoするだけの処理に脆弱性があるならサーバ側がやられます。が仮にやられてもデータを保持しておらず、ソースコードは公開しているし、怖いのは改竄くらいですが、定期的にコンテナの破棄+gitで管理されたソースでのコンテナ再構築 + 再起動がかかるので、まぁ大丈夫だろうという推定で動いています。
バイナリのレスポンスを受け取るには、XHRのresponseTypeにarraybuffer
を使用する必要があります。
出ないと文字化けした良く分からないテキスト、がレスポンスになってしまいます。
なおAjaxのライブラリ(jQueryやsuperagentあたり)を使用している場合注意が必要です。
なおソース読んでみた結果、superagentでarraybugferの指定は現状使用できないです。
Goで実装してますが、サーバ側は何の言語でも同じです。URLに対応する画像の中身を取得し、バイナリのレスポンスを吐きます。
responseType: arraybufferで受け取ったレスポンスをバイナリに変換します。
xhr.onreadystatechange = function() {
// 判定略
var buff = xhr.response;
var blob = new Blob([buff], { type: ... });
}
引数はArraybufferの配列なのでご注意下さい。
配列になっているのは複数のArrayBufferを指定できるためだそうです。
window.URL.createObjectURL()を使用して、バイナリをsrc属性に指定可能な形式に変換します。
このURLはいつでも使えるパーマリンクではなく、そのページを開いている間のみ有効なものなので、リロードすると使えなくなります。
Imageオブジェクトに自分のサーバから返されたバイナリ(画像)がセットされたので、あとは普通にcanvasで扱うだけです。
CORS制限を回避したので、canvas内で加工した画像を書き出すことができるようになっています。