Pourquoi canvas.toDataURL () lève-t-il une exception de sécurité?

90

N'ai-je pas assez dormi ou quoi? Ce code suivant

var frame=document.getElementById("viewer");
frame.width=100;
frame.height=100;

var ctx=frame.getContext("2d");
var img=new Image();
img.src="http://www.ansearch.com/images/interface/item/small/image.png"

img.onload=function() {
    // draw image
    ctx.drawImage(img, 0, 0)

    // Here's where the error happens:
    window.open(frame.toDataURL("image/png"));
}

lance cette erreur:

SECURITY_ERR: DOM Exception 18

Il n'y a aucun moyen que cela ne fonctionne pas! Quelqu'un peut-il expliquer cela, s'il vous plaît?

pop850
la source
2
Cette solution ne semble pas utile aux personnes n'utilisant pas Adobe AIR.
ObscureRobot

Réponses:

67

Dans les spécifications, il est dit:

Chaque fois que la méthode toDataURL () d'un élément canvas dont l'indicateur de nettoyage d'origine est défini sur false est appelée, la méthode doit lever une exception SECURITY_ERR.

Si l'image provient d'un autre serveur, je ne pense pas que vous puissiez utiliser toDataURL ()

Bob
la source
6
Si un attaquant est capable de deviner le nom d'une image que vous avez dans un site privé, il pourrait en obtenir une copie en peignant sur une toile et en envoyant la nouvelle image sur son site. La principale restriction de mon point de vue est d'éviter de dessiner le contenu d'un autre site, mais la sécurité est trop complexe car l'attaquant peut trouver un trou dans n'importe quel site inattendu.
AlfonsoML
3
Notez que le sous-domaine compte également. D'après mon expérience, au moins dans Chrome, une exception SECURITY_ERR: DOM 18 est déclenchée lors d'un appel qui est perçu comme appartenant à plusieurs sous-domaines: 1. dans example.com/some/path/index.html pour une vidéo ou une image dans foo .example.com 2. en accédant à la même page que dans 1 mais en entrant l'URL example.com/some/path/index.html et en essayant d'appeler toDataUrl () pour une vidéo ou une image dans www.example.com
Sam Dutton
3
@AlfonsoML, peut-être que je me trompe, mais "Si un attaquant est capable de deviner le nom d'une image que vous avez dans un site privé" il va probablement saisir l'image avec une boucle non avec un navigateur. Mon point de vue: Public URL Public Content.
kilianc
4
@ pop850 Je suis confronté à ce problème même lorsque j'utilise une URL de données. Existe-t-il un moyen de contourner ce problème pour les URL de données?
Sujay
6
@kilianc Cette restriction existe pour empêcher un attaquant de forcer votre navigateur (avec des cookies d'authentification) à récupérer une image et à envoyer son contenu à un attaquant; l'attaquant n'a pas vos cookies, il ne peut donc pas utiliser curl pour obtenir les mêmes ressources que vous êtes autorisé à obtenir. "URL publique, contenu public" est une façon de penser plutôt imparfaite: ma page Facebook comporte des composants destinés au public, mais il y en a beaucoup qui ne sont accessibles qu'avec le bon jeton d'authentification.
apsillers
18

La définition de l'attribut d'origine croisée sur les objets d'image a fonctionné pour moi (j'utilisais fabricjs)

    var c = document.createElement("img");
    c.onload=function(){
        // add the image to canvas or whatnot
        c=c.onload=null
    };
    c.setAttribute('crossOrigin','anonymous');
    c.src='http://google.com/cat.png';

Pour ceux qui utilisent fabricjs, voici comment patcher Image.fromUrl

// patch fabric for cross domain image jazz
fabric.Image.fromURL=function(d,f,e){
    var c=fabric.document.createElement("img");
    c.onload=function(){
        if(f){f(new fabric.Image(c,e))}
        c=c.onload=null
    };
    c.setAttribute('crossOrigin','anonymous');
    c.src=d;
};
Philip Nuzhnyy
la source
Merci, cela fonctionne pour moi dans Chrome. Je n'utilise pas fabric.js cependant
Philip007
J'utilise fabricjs mais je n'ai pas pu le résoudre. Comment feriez-vous cela avec ce code? function wtd_load_bg_image (img_url) {if (img_url) {var bg_img = nouvelle image (); bg_img.onload = function () {canvasObj.setBackgroundImage (bg_img.src, canvasObj.renderAll.bind (canvasObj), {originX: 'left', originY: 'top', left: 0, top: 0}); }; bg_img.src = img_url; }}
HOY
Fonctionne également dans Firefox.
vanowm
16

Si l'image est hébergée sur un hôte qui définit soit Access-Control-Allow-Origin ou Access-Control-Allow-Credentials, vous pouvez utiliser le partage de ressources cross-origin (CORS). Voir ici (l'attribut crossorigin) pour plus de détails.

Votre autre option est que votre serveur dispose d'un point de terminaison qui récupère et diffuse une image. (par exemple http: // your_host / endpoint? url = URL ) L'inconvénient de cette approche est la latence et la récupération théoriquement inutile.

S'il y a d'autres solutions alternatives, je serais intéressé à en entendre parler.

James
la source
Salut, c'est exactement ce que j'ai fait, j'ai Access-Control-Allow-Origin: * sur les images, j'ai crossorigin = 'anonymous', puis je peins l'image sur toile, puis j'appelle toDataUrl et je reçois toujours le SECURITY_ERR : DOM Exception 18
skrat
13

Il semble qu'il existe un moyen d'éviter que si l'hébergement d'images est capable de fournir les en-têtes HTTP suivants pour les ressources d'image et que le navigateur prend en charge CORS:

access-control-allow-origin: *
access-control-allow-credentials: true

Il est indiqué ici: http://www.w3.org/TR/cors/#use-cases

DitherSky
la source
1
access-control-allow-credentials: true ne fonctionnera pas avec access-control-allow-origin: *. Lorsque le premier est défini, le dernier doit avoir une valeur d'origine, pas une valeur générique de developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
Silver Gonzales
4

Enfin j'ai trouvé la solution. Il suffit d'ajouter le crossOrigintroisième paramètre dans fromURLfunc

fabric.Image.fromURL(imageUrl, function (image) {
            //your logic
    }, { crossOrigin: "Anonymous" });
jose920405
la source
2

J'ai eu le même problème et toutes les images sont hébergées dans le même domaine ... Donc, si quelqu'un a le même problème, voici comment j'ai résolu:

J'avais deux boutons: un pour générer la toile et un autre pour générer l'image à partir de la toile. Cela n'a fonctionné que pour moi, et désolé de ne pas savoir pourquoi, quand j'ai écrit tout le code sur le premier bouton. Donc, quand je clique dessus, génère la toile et l'image en même temps ...

J'ai toujours ce problème de sécurité lorsque les codes étaient sur des fonctions différentes ... = /

CarinaPilar
la source
2

J'ai pu le faire fonctionner en utilisant ceci:

Écrivez ceci sur la première ligne de votre .htaccesssur votre serveur source

Header add Access-Control-Allow-Origin "*"

Ensuite, lors de la création d'un <img>élément, procédez comme suit:

// jQuery
var img = $('<img src="http://your_server/img.png" crossOrigin="anonymous">')[0]

// or pure
var img = document.createElement('img');
img.src='http://your_server/img.png';
img.setAttribute('crossOrigin','anonymous');
Qwerty
la source
1

Vous ne pouvez pas mettre d'espaces dans votre pièce d'identité

Mettre à jour

Je suppose que l'image est sur un serveur différent de celui sur lequel vous exécutez le script. J'ai pu dupliquer votre erreur lors de son exécution sur ma propre page, mais cela a bien fonctionné au moment où j'ai utilisé une image hébergée sur le même domaine. C'est donc lié à la sécurité - mettez l'image sur votre site. Quelqu'un sait pourquoi c'est le cas?

Mike Robinson
la source
0

Si vous dessinez simplement des images sur un canevas, assurez-vous de charger les images du même domaine.

www.example.com est différent de example.com

Assurez-vous donc que vos images et l'url que vous avez dans votre barre d'adresse sont les mêmes, www ou non.

JonathanC
la source
0

J'utilise fabric.js et je pourrais résoudre ce problème en utilisant toDatalessJSON au lieu de toDataURL:

canvas.toDatalessJSON({ format: 'jpeg' }).objects[0].src

Edit: Nevermind. Il en résulte que l'image d'arrière-plan est uniquement exportée vers JPG, sans le dessin sur le dessus, ce n'est donc pas tout à fait utile après tout.

horstwilhelm
la source