Dlaczego canvas.toDataURL () zgłasza wyjątek bezpieczeństwa?

90

Nie spałem wystarczająco dużo, czy co? Poniższy kod

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"));
}

zgłasza ten błąd:

SECURITY_ERR: DOM Exception 18

Nie ma mowy, żeby to nie zadziałało! Czy ktoś może to wyjaśnić, proszę?

pop850
źródło
2
To rozwiązanie nie wydaje się przydatne dla osób nie korzystających z Adobe AIR.
ObscureRobot

Odpowiedzi:

68

W specyfikacji mówi:

Za każdym razem, gdy wywoływana jest metoda toDataURL () elementu kanwy, którego flaga origin-clean jest ustawiona na false, metoda ta musi zgłosić wyjątek SECURITY_ERR.

Jeśli obraz pochodzi z innego serwera, nie sądzę, aby można było użyć toDataURL ()

Pion
źródło
6
Jeśli napastnik jest w stanie odgadnąć nazwę obrazu, który masz na prywatnej stronie, mógłby uzyskać jego kopię, malując na płótnie i wysyłając nowy obraz do swojej witryny. Z mojego punktu widzenia głównym ograniczeniem jest unikanie rysowania zawartości innej witryny, ale bezpieczeństwo jest zbyt złożone, ponieważ osoba atakująca może znaleźć dziurę w dowolnej nieoczekiwanej witrynie.
AlfonsoML
3
Pamiętaj, że subdomena też ma znaczenie. Z mojego doświadczenia wynika, że ​​przynajmniej w przeglądarce Chrome wyjątek SECURITY_ERR: DOM 18 jest zgłaszany podczas wykonywania połączenia, które jest postrzegane jako obejmujące subdomeny: 1. w example.com/some/path/index.html w przypadku filmu lub obrazu w foo .example.com 2. przechodząc do tej samej strony co w 1, ale wprowadzając adres URL example.com/some/path/index.html, a następnie próbując wywołać toDataUrl () w celu pobrania filmu lub obrazu w witrynie www.example.com
Sam Dutton,
3
@AlfonsoML, może się mylę, ale „Jeśli atakujący jest w stanie odgadnąć nazwę zdjęcia, które masz w prywatnej witrynie”, prawdopodobnie złapie obraz z zawinięciem w przeglądarce. Mój punkt widzenia: Publiczne publiczne treści URL.
kilianc
4
@ pop850 Mam ten problem nawet wtedy, gdy używam adresu URL danych. Czy istnieje sposób obejścia tego problemu w przypadku adresów URL danych?
Sujay
6
@kilianc To ograniczenie ma na celu uniemożliwienie atakującemu spowodowania, aby Twoja przeglądarka (z uwierzytelniającymi plikami cookie) pobierała obraz i wysyłała jego zawartość do atakującego; atakujący nie ma Twoich plików cookie, więc nie może użyć curl, aby uzyskać te same zasoby, do których jesteś upoważniony. „Publiczny adres URL, zawartość publiczna” to raczej błędny sposób myślenia: moja strona na Facebooku zawiera elementy dostępne publicznie, ale wiele z nich jest dostępnych tylko za pomocą odpowiedniego tokena uwierzytelniania.
apsillers
18

Ustawienie atrybutu cross origin na obiektach obrazu działało dla mnie (używałem 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';

Dla tych, którzy używają fabricjs, oto jak załatać 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
źródło
Dzięki, u mnie działa w Chrome. Nie używam jednak
fabric.js
Używam fabricjs, ale nie mogę go rozwiązać. Jak byś to zrobił z tym kodem? funkcja wtd_load_bg_image (img_url) {if (img_url) {var bg_img = new 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
Działa również w przeglądarce Firefox.
vanowm
16

Jeśli obraz jest hostowany na hoście, który ustawia Access-Control-Allow-Origin lub Access-Control-Allow-Credentials, możesz użyć Cross Origin Resource Sharing (CORS). Zobacz tutaj (atrybut crossorigin), aby uzyskać więcej informacji.

Inną opcją jest posiadanie przez serwer punktu końcowego, który pobiera i wyświetla obraz. (np. http: // your_host / endpoint? url = URL ) Wadą tego podejścia jest opóźnienie i teoretycznie niepotrzebne pobieranie.

Jeśli istnieje więcej alternatywnych rozwiązań, byłbym zainteresowany o nich.

James
źródło
Cześć, właśnie to zrobiłem, mam Access-Control-Allow-Origin: * na obrazach mam crossorigin = 'anonymous', potem maluję obraz na płótnie, a potem dzwonię do DataUrl i nadal otrzymuję SECURITY_ERR : DOM Wyjątek 18
skrat
13

Wydaje się, że istnieje sposób, aby temu zapobiec, jeśli hosting obrazów umożliwia udostępnienie następujących nagłówków HTTP dla zasobów obrazu i przeglądarki obsługuje mechanizm CORS:

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

Stwierdzono tutaj: http://www.w3.org/TR/cors/#use-cases

DitherSky
źródło
1
access-control-allow-credentials: true nie będzie działać z access-control-allow-origin: *. Kiedy ustawiono pierwszy, drugi powinien mieć wartość pochodzenia, a nie wartość wieloznaczną z developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
Silver Gonzales
4

Wreszcie znalazłem rozwiązanie. Wystarczy dodać crossOrigintrzeci parametr w fromURLfunc

fabric.Image.fromURL(imageUrl, function (image) {
            //your logic
    }, { crossOrigin: "Anonymous" });
jose920405
źródło
2

Miałem ten sam problem i wszystkie obrazy są hostowane w tej samej domenie ... Więc jeśli ktoś ma ten sam problem, oto jak rozwiązałem:

Miałem dwa przyciski: jeden do generowania płótna i drugi do generowania obrazu z płótna. To zadziałało tylko dla mnie i przepraszam, że nie wiem dlaczego, kiedy napisałem cały kod na pierwszym przycisku. Kiedy klikam, generuje płótno i obraz w tym samym czasie ...

Zawsze mam ten problem z bezpieczeństwem, gdy kody były na różnych funkcjach ... = /

CarinaPilar
źródło
2

Udało mi się to zrobić, używając tego:

Napisz to w pierwszym wierszu .htaccessswojego serwera źródłowego

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

Następnie podczas tworzenia <img>elementu wykonaj następujące czynności:

// 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
źródło
1

Nie możesz umieszczać spacji w swoim ID

Aktualizacja

Domyślam się, że obraz znajduje się na innym serwerze niż ten, na którym wykonujesz skrypt. Udało mi się zduplikować Twój błąd podczas uruchamiania go na mojej własnej stronie, ale działało dobrze, gdy użyłem obrazu hostowanego w tej samej domenie. Jest to więc związane z bezpieczeństwem - umieść obraz na swojej stronie. Czy ktoś wie, dlaczego tak jest?

Mike Robinson
źródło
0

Jeśli po prostu rysujesz obrazy na płótnie, upewnij się, że ładujesz obrazy z tej samej domeny.

www.example.com różni się od example.com

Upewnij się więc, że Twoje obrazy i adres URL, który masz w pasku adresu, są takie same, www lub nie.

JonathanC
źródło
0

Używam fabric.js i mogę rozwiązać ten problem, używając toDatalessJSON zamiast toDataURL:

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

Edycja: nieważne. Powoduje to, że tylko obraz tła jest eksportowany do JPG, bez rysunku na górze, więc nie był on do końca przydatny.

horstwilhelm
źródło