Canvas tainted by cross-origin data - javascript

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

Javascript: Take element from website and display it on my website?

I've been trying to get the top news story from Hacker News, though an example from any website would do.
Here is my code by the way:
let getHTML = function (url, callback) {
// Feature detection
if (!window.XMLHttpRequest) return;
// Create new request
let xhr = new XMLHttpRequest();
// Setup callback
xhr.onload = function () {
if (callback && typeof (callback) === 'function') {
callback(this.responseXML);
}
};
// Get the HTML
xhr.open('GET', url);
xhr.responseType = 'document';
xhr.send();
};
getHTML('https://news.ycombinator.com/news', function (response) {
let someElem = document.querySelector('#someElementFromMyPage');
let someOtherElem = response.querySelector('#someElementFromOtherPage');
someElem.innerHTML = someOtherElem.innerHTML;
});
This should display the element from other page and bring it to my page.
When I run your code, I get a CORS error in the browser dev-tools console (more details here).
Problem
Basically the target website (https://news.ycombinator.com/news) is restricting how a Browser can request it. And the browser conforms and respects this restriction.
The JS code makes the request.
The browser reads the response and looks at the HTTP headers included in the response from (https://news.ycombinator.com/news)
Because there's X-Frame-Options: DENY and X-XSS-Protection: 1 mode=block the browser won't let you read the request in the JS code, so you get an error.
Solution
There's many options for getting around CORS errors, you can research them yourself:
Funnel requests through a proxy-server, routing CORS requests through another server that strips off the pesky CORS headers. maybe this?
Run a server for web-scraping, servers don't have to respect Headers like the browser does, so you can GET anything. maybe try this
Scraping within the browser is increasingly hard, so you need to use other solutions to take content from other sites.
Hope this helps!

How to load an image into a canvas without CORS issues for ionic iOS hybrid app

I want to load images into a canvas that is used for showing custom google map markers. The following code works for browser and Android but not iOS, as the WKWebView does not accept CORS requests for this matter.
let ctx = canvas.getContext('2d');
let img = new Image();
img.crossOrigin = "anonymous";
img.src = url;
img.onload = function () {
// process and load the image into the canvas map marker
};
img.onerror = function () {
// handle fallback
};
The images have to be loaded from google maps photos api (and other apis that I do not have control on the CORS settings on the servers). This has to be somehow handled in the app.
What possible solutions exist for this matter?
One thing that I found is ionic-image-loader (https://github.com/zyra/ionic-image-loader) that uses ionic native http requests to load and cache images into an img tag. This works from html though, and I need to use it directly in js/ts, which does not seem to be possible.
If I try to load images via ionic http request, even in browser the request is also blocked by CORS, as opposed to loading the image via img tag. Maybe if (somehow) the following header can be set to the ionic http request so that the request looks the same: Sec-Fetch-Mode: no-cors
[WKWebView]
Even the local images cannot load this way and it shows: Origin null is not allowed by Access-Control-Allow-Origin (Cannot load image: file:/// ... ) and then it's stuck in a loop trying to load the image.
Solved by using a fork of ionic-image-loader preload function with cordova native http, bypassing the webview entirely for the http request and handling the cache manually. This is how the http request is done, working on iOS WKWebView as well:
let options = {
method: 'get',
responseType: 'blob',
data: {},
headers: {}
};
cordova.plugin.http.sendRequest(url, options, (data: any) => {
let blob = data.data;
resolve(blob);
}, (error: any) => {
console.log(error.status);
console.log(error.error);
reject(error.error);
});

Secured file serving

I am trying to provide a secured file serving feature for a site. I have considered a couple of options that fit with my current model:
Using XMLHttpRequest to retrieve the contents of a file, and then offering either display or Save As features based on file type and user preference.
I understand how to use binary Ajax features (blob, arraybuffer) to retrieve binary files and I have seen this shim for implementing the HTML5 Save As feature, but I am concerned about performance with this technique re: large files. I have not tested yet but I don't expect good performance downloading a 1GB+ file using this technique.
setting an HTTP header on a link (see below) so I can pass an authentication token in the header but still have the file served directly to the browser like a normal download rather than going through the trouble of retrieving the contents with XMLHttpRequest.
I would prefer this method because it fits nicely with the current authentication framework and would require very little extra code, but as far as I can tell only Firefox supports what I want to do.
Firefox has the ability to set http headers in network calls not initiated explicitly by JavaScript code using httpChannel: Firefox HTTP channels. Does anyone know whether this functionality is supported in other browsers? I want to be able to set a link, e.g. :
window.location.href = 'blah';
and have code observing network traffic that sets a custom HTTP header:
httpChannel.setRequestHeader("X-Hello", "World", false);
While it is very cool that Firefox can do this, I need a cross-browser solution. I have seen this question:
Setting request header, in a URL
which is basically what I want to do, but the only recommended solution there is setting session cookies, which I am not using.
OK, so I built a function using option 1. from my question. Still worried about file size and performance, but here is a basic solution:
viewFile : function( fileItem ) {
var xhr = new XMLHttpRequest(), token, contentType, filename;
xhr.onreadystatechange = function() {
token = xhr.getResponseHeader( "X-Token" );
if( token !== undefined && token !== null ){
app.model.set( "token", token );
sessionStorage.token = token;
}
if ( xhr.readyState === 4 ) {
contentType = xhr.getResponseHeader( "Content-Type" );
filename = xhr.getResponseHeader( "X-Filename" );
response = xhr.response;
var blob = new Blob([xhr.response], { type: contentType } );
saveAs( blob, filename );
}
};
xhr.open( 'GET', "/index.cfm/api/filelocker/" + fileItem.fileid, true );
xhr.responseType = "arraybuffer";
xhr.setRequestHeader ( "X-Token", app.model.get( "token" ) );
xhr.send();
}
This technique uses the existing auth mechanism. I'll see how well it works with large files.

AWS S3 ajax PUT returning 'The request signature we calculated does not match the signature you provided'?

I'm trying to upload a file to S3 via a pre-signed URL. I've verified that the URL indeed works by testing it with curl
curl --request PUT --upload file {filename} "{url}"
It pushes it up there A-OK.
However, when trying it from javascript, I get this message:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
I've taken as many debugging steps as I can come up with -- for instance, making sure that the content-types and Content Lengths match between the pre-signed url and what I actually try to upload.
I found this SO Thread And tried everything in there:
My Keys have no trailing spaces or slashes
My bucketname doesn't have a slash in it.
Tried URL encoding my key -- no difference
Made sure my keyname is compliant
So, I'm at a bit of a loss. Could anyone identify what would be causing S3 to reject this request?
Javascript:
$(document).ready(function () {
$('[type=submit]').click(function (evt) {
evt.preventDefault();
console.log($('#id_attachment').get(0).files[0].size);
var reader = new FileReader();
reader.onloadend = function (evt) {
console.log(evt.target.result);
$.ajax({
url: 'https://bucketname.s3.amazonaws.com/simple.png?Signature=vYIEOmAay9v6zwB1cz78FhXv6Yo%3D&Expires=1416243285&AWSAccessKeyId=ACCESSKEY',
type: 'PUT',
contentType: "image/png",
data: evt.target.result,
success: function () {
console.log('Uploaded data successfully.');
}
});
};
reader.readAsBinaryString($('#id_attachment').get(0).files[0]);
});
});
When creating the pre-signed URL, are you specifying a Content-Type of image/png, or any Content-Type at all?
If you aren't, because you are including a Content-Type in the browser upload, the signature mismatch Amazon might be referring to is that the ajax PUT does have a Content-Type header, but signature on the signed URL does not.
I recently ran into this issue because I'm creating pre-signed URLs with the .NET AWSSDK, and not specifying a content type at the time of the URL generation.
Uploads work fine from some run-of-the-mill sample C# code, but failed in my browser app because the framework I was using automatically appended the Content-Type header, causing the mismatch. Re-playing the request with a web debbugger (I'm using Fiddler), and removing the Content-Type header from the browser's request worked, and is what led me to figuring out what was causing the mismatch for me.

File API - HTML5 App to store videos

I'd like to know if HTML5 API may fit this use case:
some videos are present on a public server (say http://videosanbox.me/video.mpg)
the JS/html5 app should store the videos locally in order to be able to play them also off-line (videos are public, there are no security
warnings)
In my initial tests I am unable to go past the "No 'Access-Control-Allow-Origin'" error.
In my understanding the following script should:
request with a get the content located at the given URL
prepare 1Mb file somewhere (I assume I'll have other errors here, but I'll get there when I'll see them:))
for now I'm interested in understanding why this error is happening, wouldn't it be normal for a client (a mobile browser) to query for resources which are not already on it?
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://videosanbox.me/video.mpg', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
window.requestFileSystem(TEMPORARY, 1024 * 1024, function(fs) {
fs.root.getFile('video.mpg', {create: true}, function(fileEntry) {
fileEntry.createWriter(function(writer) {
writer.onwrite = function(e) { alert('writing'); };
writer.onerror = function(e) { alert('error'); };
var blob = new Blob([xhr.response], {type: 'video/mpg'});
writer.write(blob);
}, onError);
}, onError);
}, onError);
};
xhr.send();
onError is just doing something in case of error: function onError(e) {console.log('Error', e);}
Solution 1
On the server side, you need this header to be passed:
header('Access-Control-Allow-Origin: *');
Unfortunately, that may not be possible if you don't have control over videosanbox.me. If not, contact them and see if they're willing to allow this. If not, don't worry, there's a second solution:
Solution 2
Create a web page on your own server and allow cross site scripting (but use security to restrict who can use this page). In that page's code, it will take the request and open an HTTP connection to http://videosanbox.me, retrieve the mpg file and spit it back to the PhoneGap app as a Blob. Your PhoneGap would connect to this page (on your server) via Ajax instead of http://videosanbox.me.

Categories