<title>測試Canvas來源安全原則</title>
<script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.4.js">
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
$("#b1").click(function () { //畫上漸層線
for (var i = 0; i < 10; i++) {
ctx.strokeStyle = "rgb(0,0," + i * 25 + ")";
$("#b2").click(function () { //由同網站取得圖檔
var $img = $("<img />", { src: "DarkthreadPlurk.jpg" });
ctx.drawImage(this, 5, 5, 90, 90);
$("#b3").click(function () { //由其他站台取得圖檔
var $img = $("<img />", {
src: "http://avatars.plurk.com/3405911-big3.jpg"
ctx.drawImage(this, 105, 5, 90, 90);
$("#b4").click(function () { //使用toDataURL()匯出圖檔
$("#p").attr("src", canvas.toDataURL());
$("#b5").click(function () { //使用getImageData()取得像素資料
var id = ctx.getImageData(0, 0, 1, 1);
.std { border: 1px solid black; display: block; margin: 5px; }
<input type="button" id="b1" value="Draw Lines" />
<input type="button" id="b2" value="Load Local Image" />
<input type="button" id="b3" value="Load Remote Image" />
<canvas id="c" class="std" width="200" height="100"></canvas>
<input type="button" id="b4" value="Export Image" />
<input type="button" id="b5" value="Export Pixel Array" />
<img id="p" class="std" />
網頁上有一個canvas,按下b1鈕(Draw Lines)會在canvas上塗滿漸層橫條、按下b2(Load Local Image)會由同一網站載入圖檔,以drawImage()繪製在左半邊、按下b3(Load Remote Image)則會由Plurk網站取得同樣的圖檔顯示在右半邊。
按下b4(Export Image)鈕會呼叫canvas.toDataURL()將canvas的內容輸出在下方 的<img>,按下b5(Export Pixel Array)則會呼叫context.getImageData(0, 0, 1, 1)取得最左上角的像素數據alert()出來。
測試時,若只按下b1或b2,尚可順利執行toDataURL()或getImageData(),一旦按下b3由外部網站取得圖檔加入canvas,之後再試著按b4或b5都會引發如上圖的錯誤!!
既然是瀏覽器基於安全性考量設立的限制,一般來說就不太有閃躲迴避的空間,我想到比較簡單直覺的繞路法是在同一網站上用ASP.NET或其他伺服器端語言寫一小段程式,以伺服器身分去遠端網站取回圖檔內容,再將byte[]抛回給瀏覽器,喬裝圖檔來自同一網站。
<%@ WebHandler Language="C#" Class="ImageProxy" %>
using System;
using System.Web;
using System.Net;
public class ImageProxy : IHttpHandler {
public void ProcessRequest (HttpContext context) {
HttpRequest req = context.Request;
HttpResponse resp = context.Response;
try
{
//限定透過POST,避免被其他網站當作下載跳板
if (req.HttpMethod == "POST")
{
string url = req.Form["url"];
if (string.IsNullOrEmpty(url)) return;
string mime = "";
switch (url.Substring(url.Length - 4, 4).ToLower())
{
case ".jpg":
mime = "image/jpeg";
break;
case ".png":
mime = "image/png";
break;
case ".gif":
mime = "image/gif";
break;
default:
return;
}
WebClient wc = new WebClient();
byte[] b = wc.DownloadData(url);
resp.Write(string.Format("data:{0};base64,{1}",
mime, Convert.ToBase64String(b)));
}
}
catch (Exception ex)
{
resp.ContentType = "text/plain";
resp.Write(ex.Message);
}
}
public bool IsReusable {
get {
return false;
}
}
}
$("#b3").click(function () { //透過Proxy ASP.NET由其他站台取回圖檔
$.post("ImageProxy.ashx", {
url: "http://avatars.plurk.com/3405911-big3.jpg"
}, function (r) {
if (r.indexOf("data:") != 0) alert(r);
else {
var $img = $("<img />", { src: r });
$img.load(function () {
ctx.drawImage(this, 105, 5, 90, 90);
});
}
});
});