Why CORS on Images with HTML Canvas?

2019-01-25 18:54发布

问题:

Recently, I spent a bit of time researching a solution to a rather common problem in web development- I was dealing with logos middle-aligned on a transparent background, but having to place text below them, it would then appear as though the amount of whitespace between the text and the image shifts from page to page. After a bit of research, I discovered I could re-align the images bottom-left using a canvas, and the solution worked beautifully... at least until I integrated the solution into our code-base and discovered it was failing with:

"Unable to get image data from canvas because the canvas has been tainted by cross-origin data." (Say what!?)

Looking into it, the offending code was located at the first line of the following function:

getColumn: function (context, x, y, imageHeight) {
    var arr = context.getImageData(x, y, 1, imageHeight).data; //<-- CORS HATES THIS!
    return pvt.normalizeRGBArray(arr)
}

Now, I understand perfectly well what the CORS standard is, and I know the solution to this problem. The server needs to support CORS first. Either the server can set the http header access-control-allow-origin: "*", or allow a developer to set the crossDomain attribute for the image to "anonymous"/"use-credentials". This is all fine and dandy except when you work on the front-end for a big company where convincing the server-lords to change anything related to security is a non-starter conversation.

So really, my question here is what IS the logic behind having this security error occur with images on a canvas? They are frickin' images for crying out loud! It is fine to download them, hot-link to them, use them in memory, but "oh no!" don't manipulate them in any way whatsoever, or CORS throws an error!

If you ask me, the image is not tainted, it's this hairbrained CORS standard that is. Can somebody please explain the logic for why this happens? How could using a canvas to manipulate an image possibly be a security concern?

回答1:

This protects users from having private data exposed by using images to pull information from remote web sites without permission.

Source: MDN



回答2:

Sorry, not an answer to the question but ...

FYI: It's not

Either the server can set the http header access-control-allow-origin: "*", or allow a developer to set the crossDomain attribute for the image to "anonymous"/"use-credentials".

BOTH are required.

You need to set crossOrigin because it changes the request the browser makes to the server for the image. But even if you don't set it, and the server sends the CORS headers anyway the browser will still not let you use the image unless you had set crossOrigin.

You can see it in this example, two images, both of which receive CORS headers from the server but the browser only lets one work.

loadAndDrawImage("https://i.imgur.com/fRdrkI1.jpg", "");
loadAndDrawImage("https://i.imgur.com/Vn68XJQ.jpg");

function loadAndDrawImage(url, crossOrigin) {
  const img = new Image();
  img.onload = function() { 
    log("For image", crossOrigin !== undefined ? "WITH" : "without", "crossOrigin set");
    try {
      const ctx = document.createElement("canvas").getContext("2d");
      ctx.drawImage(img, 0, 0);
      ctx.getImageData(0, 0, 1, 1);
      log("canvas still clean:", name);
    } catch (e) {
      error(name, ":", e);
    }
    log(" ");
  };  
  if (crossOrigin !== undefined) {
    img.crossOrigin = crossOrigin;
  }
  img.src = url;
}

function logImpl(color, ...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(" ");
  elem.style.color = color;
  document.body.appendChild(elem);
}

function log(...args) {
  logImpl("green", ...args);
}

function error(...args) {
  logImpl("red", ...args);
}
pre { margin: 0; }
<div>check headers in devtools</div>

If you check the header you'll see they both received CORS headers but only one image worked.