1

How can i allow the users of my website to download the content of an HTML canvas as a PNG file?

The problem is, that I am drawing images from different origins onto this canvas. (The user can insert the URL of an image, and that is drawn onto the canvas). Therefore I cannot use toDataURL(), toBlob() or similar, because they will throw a

DOMException: The operation is insecure.

for cross-origin reasons. As far as I know, the goal of this restriction is to prevent the website from sending the canvas data back to the server, which could leak private data of the user, for example if only he had access to the URL of the image that was drawn.

But allowing the user to download the canvas as a png is not a security risk itself, which makes me think there must be a safe way to do it. But how?

Or is there a way to load the image from a public point of view, instead of the users point of view, which would also get rid of the security risk (assuming my understanding for this restriction is right)?

Heretic Monkey
  • 11,687
  • 7
  • 53
  • 122
FireFuro99
  • 357
  • 1
  • 5
  • 18
  • How are you requesting the image? – Quentin Apr 06 '23 at 18:00
  • @Quentin By setting it as the "src" of an Image object. `const image = new Image(); image.src = ; image.onload = () => { // draw to canvas };` – FireFuro99 Apr 06 '23 at 18:01
  • Actually, [PNGs can be a vector for infection](https://www.bleepingcomputer.com/news/security/worok-hackers-hide-new-malware-in-pngs-using-steganography/)... – Heretic Monkey Apr 06 '23 at 18:02
  • 1
    @FireFuro99 — Then [this is a duplicate](https://stackoverflow.com/questions/22901073/securityerror-the-operation-is-insecure-using-htmlcanvas). – Quentin Apr 06 '23 at 18:03
  • @Quentin I tried that, that doesn't work, the server does not support CORS. – FireFuro99 Apr 06 '23 at 18:04
  • @FireFuro99 — I refer you to the last paragraph of the accepted answer of the duplicate. – Quentin Apr 06 '23 at 18:05
  • @Quentin But that's a server-side solution which isn't an option. So there is no client side way to do it? Why not? How is it a risk to allow the user to download it? I don't understand... – FireFuro99 Apr 06 '23 at 18:05
  • @FireFuro99 — the goal of this restriction is to prevent the website from sending the canvas data back to the server, which could leak private data of the user, for example if only he had access to the URL of the image that was drawn. – Quentin Apr 06 '23 at 18:06
  • @Quentin But can you not allow the user to download it without the client being able to send it back? – FireFuro99 Apr 06 '23 at 18:07
  • 1
    @FireFuro99 — There's no API to download a canvas as an image. – Quentin Apr 06 '23 at 18:07
  • @Quentin Ok thanks, that solves my question. That sucks. – FireFuro99 Apr 06 '23 at 18:08

1 Answers1

2

You are right, this limitation is only to prevent the website to access data it shouldn't have access to. And this is why browsers that do allow to save canvas as image still do even if the canvas has been tainted.

So (in all but Safari) your users can actually download the tainted canvas on their computer, by right-clicking the canvas, and then select "Save As Image" in the context menu:

const img = new Image();
img.src = "https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png";
img.decode().then(() => {
  const canvas = document.querySelector("canvas");
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext("2d");
  ctx.filter = "invert(1)";
  ctx.drawImage(img, 0, 0);
  // just to be sure we tainted the canvas as expected
  try {
    canvas.toDataURL();
    console.error("o_0 canvas is not tainted");
  } catch (err) {
    console.log("Canvas is tainted. Right-click it and 'Save Image As...'");
  }
});
<canvas></canvas>

However your website can't access this data in any form, and it can't either force this "Save As Image" feature.
Also worth noting that Safari doesn't allow saving <canvas> as images on disk (whether the <canvas> is tainted or not), so for your Safari users they will indeed be unable to save the resulting image on disk, short of taking a screenshot manually.

Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Oh that's cool, i didn't know/expect that you could just "Save As Image" via contextmenu on a default canvas. Too bad there is no "Copy Image" option, only "Save image" and "Open in new tab". But I guess that will do, thank you! – FireFuro99 Apr 07 '23 at 19:00