I'm loading a motion jpeg from third-party site, which I can trust. I'm trying to getImageData() but the browser (Chrome 23.0) complains that:
Unable to get image data from canvas because the canvas has been tainted by
cross-origin data.
There are some similar questions on SO, but they are using local file and I'm using third party media. My script runs on a shared server and I don't own the remote server.
I tried img.crossOrigin = 'Anonymous' or img.crossOrigin = '' (see this post on the Chromium blog about CORS), but it didn't help. Any idea on how can I getImageData on a canvas with cross-origin data? Thanks!
You cannot reset the crossOrigin flag once it is tainted, but if you know before hand what the image is you can convert it to a data url, see Drawing an image from a data URL to a canvas
But no, you cannot and should not be using getImageData() from external sources that don't support CORS
While the question is very old the problem remains and there is little on the web to solve it. I came up with a solution I want to share:
You can use the image (or video) without the crossorigin attribute set first and test if you can get a HEAD request thru to the same resource via AJAX. If that fails, you cannot use the resource. if it succeeds you can add the attribute and re-set the source of the image/video with a timestamp attached which reloads it.
This workaround allows you to show your resource to the user and simply hide some functions if CORS is not supported.
HTML:
<img id="testImage" src="path/to/image.png?_t=1234">
JavaScript:
var target = $("#testImage")[0];
currentSrcUrl = target.src.split("_t=").join("_t=1"); // add a leading 1 to the ts
$.ajax({
url: currentSrcUrl,
type:'HEAD',
withCredentials: true
})
.done(function() {
// things worked out, we can add the CORS attribute and reset the source
target.crossOrigin = "anonymous";
target.src = currentSrcUrl;
console.warn("Download enabled - CORS Headers present or not required");
/* show make-image-out-of-canvas-functions here */
})
.fail(function() {
console.warn("Download disabled - CORS Headers missing");
/* ... or hide make-image-out-of-canvas-functions here */
});
Tested and working in IE10+11 and current Chrome 31, FF25, Safari 6 (Desktop).
In IE10 and FF you might encounter a problem if and only if you try to access http-files from a https-script. I don't know about a workaround for that yet.
UPDATE Jan 2014:
The required CORS headers for this should be as follows (Apache config syntax):
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "referer, range, accept-encoding, x-requested-with"
the x-header is required for the ajax request only. It's not used by all but by most browsers as far as I can tell
Also worth noting that the CORS will apply if you are working locally regardless of if the resource is in the same directory as the index.html file you are working with. For me this mean the CORS problems disappeared when I uploaded it to my server, since that has a domain.
You can use base64 of the image on canvas,
While converting into base64 you can use a proxy URL (https://cors-anywhere.herokuapp.com/) before your image path to avoid cross-origin issue
check full details here
https://stackoverflow.com/a/44199382/5172571
var getDataUri = function (targetUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
var proxyUrl = 'https://cors-anywhere.herokuapp.com/';
xhr.open('GET', proxyUrl + targetUrl);
xhr.responseType = 'blob';
xhr.send();
};
getDataUri(path, function (base64) {
// base64 availlable here
})
Related
I have an old Chrome extension that added drag-and-drop functionality to a site with file uploads. The site has pages on one subdomain (ex: https://foo.site.com/page1.php, https://foo.site.com/page2.php..), and the upload endpoint is on another subdomain (ex: https://bar.site.com/upload.php). Starting in some previous Chrome version, the extension stopped working because of stricter CORS rules. The POST to upload the file seems to succeed, but the response is blocked with a CORB error, and I need the response to know where the file's new remote path is.
I've attempted to refactor by moving requests from the content script to the background script, per Google's guidelines. However, I can't figure out how to share a file drag-and-dropped onto the page with the background script. Here's some sample code:
content.js:
var setupDragAndDrop = function () {
(
document.getElementById("some_element")
).addEventListener("drop", function (e) {
for (var t = 0, s = e.dataTransfer.files.length; t < s; t++)
upload(e.dataTransfer.files[t], function (e) {
insertResponseOnPage(e); // Use the upload response to show the new remote path on the page
});
e.preventDefault();
});
},
var upload = function(file, callback) {
chrome.runtime.sendMessage(
{
need: "upload",
file: file,
callback: callback
}
);
}
background.js (there is some other logic that lets the message end up at this method, but it does end up here with the file and callback):
var upload = function(file, callback) {
var formData = new FormData();
formData.append('file', file);
const UPLOAD_URL = 'https://bar.site.com/upload.php';
var xhr = new XMLHttpRequest();
xhr.open('POST', UPLOAD_URL, true);
xhr.withCredentials = "true";
xhr.onload = callback;
xhr.send(formData);
};
This issue I'm seeing is that the file ending up on the background page is an empty object. I believe I read somewhere that whatever is sent with the message API needs to be JSON serializable, so I'm assuming File is not? I get the same behavior if I attempt to send a FormData object as well. I've also tried going down the route of somehow getting the file path instead and recreating the file in the background page, but apparently you can't get the path from a system file in JS for security reasons.
Is there any way to fix up this extension? I do not own the site this extension attempts to add functionality to, so I can't move the upload endpoint onto the same subdomain.
I need a way to insert the text downloaded from a .txt file from a URL into an element or variable which i can use further.
I have tried adding the URL to an object element which displays the text correctly, but I do not know how to add this text into a variable.
var storage = firebase.storage();
var storageRef = storage.ref();
var tangRef = storageRef.child('Recs');
var fileRef = tangRef.child("rec3.txt");
fileRef.getDownloadURL().then(function(url)
{
alert(url);
var para = document.getElementById('p1');
var par = document.createElement("object");
par.setAttribute('data', url);
para.appendChild(par);
}).catch(function(error)
{
console.error(error);
});
As mentionned, you can get some file through an object HTML element as you can do through a script HTML element to load a file. From these elements you won't have a same-origin policy problem, that's why your document is loaded with setAttribute and appendChild.
If you tried to access a resource by XHR or if you tried to interact with a document (both by JS), which are not from the same origin than your current resource, you will need to manage a same-origin policy mechanism see : https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy
You can choose XHR or choose to access the nested document from the object HTML element, in both case you will have the same-origin policy problem. This is for security reasons which are linked with JavaScript.
If you choose nested document you could do something like this :
<div id="p1"></div>
<script>
var url = "https://firebasestorage.googleapis.com/v0/b/ninjatest-1b0ab.appspot.com/o/random%20text%20file.txt?alt=media&token=c09ae3ee-6a01-4f2b-b2b3-2f57ed7ff111";
// or
// var url = "http://localhost:4000/file.txt";
var para = document.getElementById('p1');
var par = document.createElement("object");
par.setAttribute('data', url);
para.appendChild(par);
par.onload = function() {
var doc = par.contentDocument || par.contentWindow.document;
var data = doc.body.childNodes[0].innerHTML;
console.log(data);
};
</script>
If you run this code, you can see that it doesn't accept cross-origin. This is because i'm trying to get a document (nested in the HTML document) which is from another domain. The browser won't let me access it. In the other hand, if i run in local with a node server, it allows me to get it without the error.
If you choose to use XHR (XMLHttpRequest) you can do something like that :
var data;
var url = "https://firebasestorage.googleapis.com/v0/b/ninjatest-1b0ab.appspot.com/o/random%20text%20file.txt?alt=media&token=c09ae3ee-6a01-4f2b-b2b3-2f57ed7ff111";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
data = xhr.responseText;
console.log(data);
}
};
xhr.open('GET', url);
xhr.send();
Again here, it won't work because of the different origin. In the two situations, it's the browser which implements a security rule. You can fix it if you have access to the server part. On the server, you could tell the browser (by HTTP header) to allows client from different origin.
With XHR you need to search about CORS.
With Nested document, you can look here :
SecurityError: Blocked a frame with origin from accessing a cross-origin frame
How Can I Bypass the X-Frame-Options: SAMEORIGIN HTTP Header?
If you don't have access to the server part, you could grab the file with a GET request from a server that you own (and so have access to the server part). In this case, you won't have the browser security issue because from your server, you will serve the file without the restriction of same-origin. It will be a proxy server solution.
With Firebase
When you create project with Firebase you can configurate the server part to allow the XHR as mention here : https://firebase.google.com/docs/storage/web/download-files#download_data_via_url
Firstly install Google Cloud SDK to have gsutil : https://cloud.google.com/storage/docs/gsutil_install#install
Then create a .json file on your computer : https://cloud.google.com/storage/docs/configuring-cors#configure-cors-bucket
Then execute this command : gsutil cors set cors.json gs://<your-cloud-storage-bucket>
JSON file example :
[
{
"origin": ["*"],
"method": ["GET"],
"maxAgeSeconds": 3600
}
]
I created a Firebase account, tried it and it works very well.
Working example with XHR (you can run it) :
var data;
var url = "https://firebasestorage.googleapis.com/v0/b/first-app-a7872.appspot.com/o/firebase.txt?alt=media&token=925fef9e-750e-40e5-aa92-bdfe8204d32e";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
data = xhr.responseText;
console.log(data);
}
};
xhr.open('GET', url);
xhr.send();
https://codepen.io/anon/pen/JmoqQY?editors=1010
In HTML file:
<div id="p1">
</div>
script
url = "https://firebasestorage.googleapis.com/v0/b/ninjatest-1b0ab.appspot.com/o/random%20text%20file.txt?alt=media&token=c09ae3ee-6a01-4f2b-b2b3-2f57ed7ff111"
var para = document.getElementById('p1');
var par = document.createElement("object");
par.setAttribute('data', url);
para.appendChild(par);
EDIT: I made a stackblitz to try to see if I could extract the data in the text file into a variable for manipulation.
https://stackblitz.com/edit/angular-eyhaj5
I was unable to find a way. Problem is that because of CORS policy in browsers you are not supposed to open files outside of your own site.
To get around this you have to either have to send the URL to a function on your server that downloads the textFile and then makes it accessible in a folder.
Or you could set up a proxy server that allows cors. Or you could ask the owner of the text file to make it into an API.
Possibly it was a bad idea to put the textfile in firestorage in the first place. If you are the owner of the text file, it would maybe be better to put the text in a firestore database rather than save it as a textfile.
-----update------
I commented out the window.URL.revokeObjectURL( imgSrc ); and now the call works in all browsers. It seems like the url was being revoked too soon in Chrome. I would still be very curious to know why this is, and to know if there are any problems in anyone's opinion with the way I am handling revoking the URLs now. I now revoke the last URL as the next one is loaded with if (imgSrc) {window.URL.revokeObjectURL( imgSrc );} (imgSrc is now a global variable).
I have a web site which uses AJAX to call a CGI script, which outputs image/jpeg data, and the blob response is then set as the src of an image on the page using the createObjectURL function. At present it works in all browsers but Chrome.
In all other browsers the image is displayed, but in Chrome I get the message: Failed to load resource: the server responded with a status of 404 (Not Found) followed by a url prefixed by blob:.
I have tried using webkitURL instead, but this gives me the same error followed by webkitURL is deprecated.
This is the code of my AJAX call:
var filepath = "/babelia.cgi";
xmlhttp=new XMLHttpRequest();
xmlhttp.onload = function(oEvent) {
if (imgSrc) {window.URL.revokeObjectURL( imgSrc );}
var blob = xmlhttp.response;
if (endless) {
imgSrc = (window.URL ? URL : webkitURL).createObjectURL( blob );
document.getElementById("palette").src = imgSrc;
// window.URL.revokeObjectURL( imgSrc );
babel();
}
}
xmlhttp.open("POST",filepath,true);
xmlhttp.responseType = "blob";
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
var info = "location=" + postdata;
if (!landscape) {info += "&flip=portrait";}
xmlhttp.send(info);
You can see the website here: https://babelia.libraryofbabel.info/slideshow.html
I had some issues understanding blobs as well, here are my 2 cents.
I recommend using https://github.com/eligrey/Blob.js for abstracting away browser differences when working with blobs.
When debugging chrome check out chrome://blob-internals/ for details. With the current version (47.0.2526.106) it shows the urls and the associated data or references to local files for those urls.
As soon as you revoke the url you basically tell the browser that you no longer need the data, which is good if you want to free memory, but bad if you still have references pointing to that url (e.g. an img tag).
Side note: reading your question made me solve of my own problem which was that I was seeing 404s in chrome after revoking because I still had a reference to a revoked url on an img tag.
I'm loading a motion jpeg from third-party site, which I can trust. I'm trying to getImageData() but the browser (Chrome 23.0) complains that:
Unable to get image data from canvas because the canvas has been tainted by
cross-origin data.
There are some similar questions on SO, but they are using local file and I'm using third party media. My script runs on a shared server and I don't own the remote server.
I tried img.crossOrigin = 'Anonymous' or img.crossOrigin = '' (see this post on the Chromium blog about CORS), but it didn't help. Any idea on how can I getImageData on a canvas with cross-origin data? Thanks!
You cannot reset the crossOrigin flag once it is tainted, but if you know before hand what the image is you can convert it to a data url, see Drawing an image from a data URL to a canvas
But no, you cannot and should not be using getImageData() from external sources that don't support CORS
While the question is very old the problem remains and there is little on the web to solve it. I came up with a solution I want to share:
You can use the image (or video) without the crossorigin attribute set first and test if you can get a HEAD request thru to the same resource via AJAX. If that fails, you cannot use the resource. if it succeeds you can add the attribute and re-set the source of the image/video with a timestamp attached which reloads it.
This workaround allows you to show your resource to the user and simply hide some functions if CORS is not supported.
HTML:
<img id="testImage" src="path/to/image.png?_t=1234">
JavaScript:
var target = $("#testImage")[0];
currentSrcUrl = target.src.split("_t=").join("_t=1"); // add a leading 1 to the ts
$.ajax({
url: currentSrcUrl,
type:'HEAD',
withCredentials: true
})
.done(function() {
// things worked out, we can add the CORS attribute and reset the source
target.crossOrigin = "anonymous";
target.src = currentSrcUrl;
console.warn("Download enabled - CORS Headers present or not required");
/* show make-image-out-of-canvas-functions here */
})
.fail(function() {
console.warn("Download disabled - CORS Headers missing");
/* ... or hide make-image-out-of-canvas-functions here */
});
Tested and working in IE10+11 and current Chrome 31, FF25, Safari 6 (Desktop).
In IE10 and FF you might encounter a problem if and only if you try to access http-files from a https-script. I don't know about a workaround for that yet.
UPDATE Jan 2014:
The required CORS headers for this should be as follows (Apache config syntax):
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "referer, range, accept-encoding, x-requested-with"
the x-header is required for the ajax request only. It's not used by all but by most browsers as far as I can tell
Also worth noting that the CORS will apply if you are working locally regardless of if the resource is in the same directory as the index.html file you are working with. For me this mean the CORS problems disappeared when I uploaded it to my server, since that has a domain.
You can use base64 of the image on canvas,
While converting into base64 you can use a proxy URL (https://cors-anywhere.herokuapp.com/) before your image path to avoid cross-origin issue
check full details here
https://stackoverflow.com/a/44199382/5172571
var getDataUri = function (targetUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
var proxyUrl = 'https://cors-anywhere.herokuapp.com/';
xhr.open('GET', proxyUrl + targetUrl);
xhr.responseType = 'blob';
xhr.send();
};
getDataUri(path, function (base64) {
// base64 availlable here
})
I want to perform a javascript xhr request for a png file from a C# webserver which I wrote.
Here is the code I use
var imgUrl = "http://localhost:8085/AnImage.png?" + now;
var request = new XMLHttpRequest();
request.open('GET', imgUrl, false);
request.send(); // this is in a try/catch
On the server-side I send back the file and add a Content-Disposition header.
I obtain the following response
I made sure that Content-Disposition was attached in the headers after the Content-Type (the screenshot is from Firebug, which appends in alphabetical order).
The results is that no dialog box is triggered, am I missing something in the response?
edit:
I want to perform everything in javascript for several reasons.
First: I don't want to show the image and I want to keep everything behind the curtain.
Second: when requesting the image I want the Content-Disposition to be added only on particular requests. Such requests are marked with a "Warning" header with value "AttachmentRequest"
request.setRequestHeader("Warning","AttachmentRequest");
I don't think Content-Disposition triggers any file save dialog when the request is via XHR. The use of XHR suggests you're going to handle the result in code.
If you want the user to be prompted to save the image to a file, I've used this technique successfully:
window.open("http://localhost:8085/AnImage.png?" + now);
It has the downside that it flashes a blank open window briefly until the header arrives, then the new window closes and the "save file" dialog box appears.
Using an iframe may prevent the window flashing:
var f = document.createElement('iframe');
f.style.position = "absolute";
f.style.left = "-10000px";
f.src = "http://localhost:8085/AnImage.png?" + now;
document.body.appendChild(f);
Separately, I wonder what effect (if any) Content-Disposition has on the handling of an img element:
var img = document.createElement('img');
img.style.position = "absolute";
img.style.left = "-10000px";
img.src = "http://localhost:8085/AnImage.png?" + now;
document.body.appendChild(img);
I haven't tried that, but the browser might respect the header. You'd need to be sure to test on all of the browsers you want to support.