【问题标题】:How to check, if a canvas element has been tainted?如何检查画布元素是否已被污染?
【发布时间】:2014-04-29 19:35:51
【问题描述】:

基本场景

我正在客户端加载几张图片。其中一些来自另一个域,一些不是。有些我可以使用crossOrigin attribute 访问,有些则不能。

基本要求是尽可能检索图像的dataURL

问题

在将图像绘制到画布元素之后(我需要获取一个 dataURL,对吗?),如果没有 try ... catch 块,我如何检查画布是否已被污染?如果画布被污染了,我就不能再使用toDataURL() (see MDN)。

var image = new Image(),
    canvas = document.createElement( 'canvas' ),
    context = canvas.getContext( '2d' );

image.onload = function(){

  // adjust dimensions of canvas
  canvas.width = image.width;
  canvas.height = image.height;

  // insert image
  context.drawImage( image, 0, 0 );

  // how to check here?

  // get dataurl
  dataurl = tmpCanvas.toDataURL();

  // do stuff with the dataurl
};
image.src = src;  // some cross origin image

【问题讨论】:

  • 这是一个很好的问题。除了尝试/捕获之外,我不知道任何其他方式。但是,如果您引用了image 变量,则可以调用image.crossOrigin。扩展这个想法,您可以创建一个全局对象,将图像映射到它们对应的 crossOrigin 变量。检查图像是否被污染将是遍历对象并查看 crossOrigin 值的问题。
  • 此外,画布上下文似乎有一个内部 origin-clean 变量:w3.org/TR/html5/scripting-1.html#concept-canvas-origin-clean 但据我所知,这不会向用户公开。
  • @monsur 是浏览器内部的(规范适用于浏览器实现者)。
  • @Cryptoburner 是的,这是真的。但是下面的讨论表明了对这个属性的良好需求。这应该在 HTML5 规范中提出。

标签: javascript html5-canvas cors


【解决方案1】:

这是一个不向原生对象添加属性的解决方案:

function isTainted(ctx) {
    try {
        var pixel = ctx.getImageData(0, 0, 1, 1);
        return false;
    } catch(err) {
        return (err.code === 18);
    }
}

现在只需这样做检查:

if (isTainted(ctx)) alert('Sorry, canvas is tainted!');

编辑:现在我看到你想要一个没有 try-catch 的解决方案。不过,这是检查的正确方法,因为没有向用户公开原始清理标志(仅供内部使用)。 不建议向原生对象添加属性。

【讨论】:

  • 是的,因为 CORS 错误是致命的,所以这个快速的 if-else 测试也失败了:-(。我同意应该有一个暴露的标志来指示一个受污染的画布。即使这个问题明确地问了,我可能也会在这里使用 try-catch。毕竟提问者并没有试图消除由糟糕的编码造成的错误。他只是试图确定画布的状态。
  • 跳上我的肥皂盒 ;-) 我会说“不建议修改本机对象的现有属性的功能”。恕我直言,javascript 的本质是一种开放的原型语言,它鼓励您创建和扩展对象。那是我的 2 美分;)
  • @markE 是的,它是动态的(我们喜欢它的一个原因!)但是使用我们自己的对象;)在这种情况下它很可能没有任何伤害,但总的来说,如果可以的话,它被认为是不好的做法以其他方式解决(在这种情况下可以)。
  • 再谈soapbox :-) 在原生对象上添加(而不是替换)属性/方法很常见。甚至 John Resig(jQuery 的创造者!)也扩展了原生对象(例如:ejohn.org/blog/fast-javascript-maxmin)当然,如果您不喜欢添加属性,您可以将原生对象封装到您自己的对象中并扩展您自己的对象。或者只是创建一个数组来保存哪些图像被污染。很多变通方法来适应每个程序员的风格!无论如何,SO 的自动版主会因为这次讨论而生我们的气——但和你讨论很有趣! :-)
  • @markE 如果需要,我不介意更改对象。我的观点是,如果“你知道你在做什么”就没有问题。但是,会有初学者阅读这些帖子,我认为明智的做法是不要给人这种做法完全可以的印象,因为经验表明事实并非如此(如果您知道自己在做什么) '正在做)。最好在 SO IMO 这样的地方保持安全,当用户有足够的经验时,他们会弄清楚什么是好,什么不好。但我不打算开始任何讨论——这只是一个建议,我认为这是一个很好的建议。 jQuery 是一个特例。
【解决方案2】:

这是一个不使用 try-catch 的 CORS 污染的间接测试:

演示:http://jsfiddle.net/m1erickson/uDt2K/

它通过在加载图像之前设置 image.tainted=true 标志来工作

然后在 image.onload 中,context.getImageData 触发/不触发 CORS 违规。

如果没有发生违规,则污染标志设置为 false (image.tainted=false)。

var img=new Image();

// set a "tainted flag to the image to true (initialize as tainted)
img.tainted=true;

img.onload=function(){

    // try an action that triggers CORS security

    var i=ctx.getImageData(1,1,1,1);

    // if we don't get here, we violated CORS and "tainted" remains true

    // if we get here, CORS is happy so set the "tainted" flag to false

    img.tainted=false;

};

// test with tainted image

img.src="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";

由于 image.onload 是异步的,即使在违反 CORS 之后,您在 image.onload 之外的代码仍会执行。

下面是示例代码:

  • 创建一个图像对象
  • 测试图像是否符合 CORS 标准
  • 如果图像符合要求,则执行一次回调
  • 如果图像不合规,则执行另一个回调

示例代码:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // known CORS violator
    var src1="http://pp-group.co.uk/wp/wp-content/uploads/2013/10/house-illustration-web.gif";
    // known CORS compliant
    var src2="https://dl.dropboxusercontent.com/u/139992952/houseIcon.png";

    // callbacks depending on if the image causes tainted canvas
    function tainted(img){console.log("tainted:",img.src);}
    function notTainted(img){console.log("not tainted:",img.src);}

    // testing
    var image1=newImage(src1,tainted,notTainted);
    var image2=newImage(src2,tainted,notTainted);


    function newImage(src,callWhenTainted,callWhenNotTainted){

        // tmpCanvas to test CORS
        var tmpCanvas=document.createElement("canvas");
        var tmpCtx=tmpCanvas.getContext("2d");
        // tmpCanvas just tests CORS, so make it 1x1px
        tmpCanvas.width=tmpCanvas.height=1;

        var img=new Image();
        // set the cross origin flag (and cross our fingers!)
        img.crossOrigin="anonymous";
        img.onload=function(){
            // add a tainted property to the image 
            // (initialize it to true--is tainted)
            img.tainted=true;
            // draw the img on the temp canvas
            tmpCtx.drawImage(img,0,0);
            // just in case this onload stops on a CORS error...
            // set a timer to call afterOnLoad shortly
            setTimeout(function(){
                afterOnLoad(img,callWhenTainted,callWhenNotTainted);
            },1000);  // you can probably use less than 1000ms
            // try to violate CORS
            var i=tmpCtx.getImageData(1,1,1,1);
            // if we get here, CORS is OK so set tainted=false
            img.tainted=false;
        };
        img.src=src;
        return(img);
    }

    // called from image.onload
    // at this point the img.tainted flag is correct
    function afterOnLoad(img,callWhenTainted,callWhenOK){
        if(img.tainted){
            // it's tainted
            callWhenTainted(img);
        }else{
            // it's OK, do dataURL stuff
            callWhenOK(img);
        }
    }


}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="canvas" width=360 height=281></canvas>
</body>
</html>

【讨论】:

  • 以防万一读者不清楚,这仍然会引发未捕获的错误,它只是被同步代码忽略,因为它会在异步流程中引发。在某些环境中,未捕获的异步异常会停止程序(无论如何您都应该捕获它们)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-07
  • 1970-01-01
  • 2014-04-01
相关资源
最近更新 更多