I\'m trying to detect when an XMLHttpRequest() fails due to a Cross Origin Error as opposed to a bad request. For example:
ajaxObj=new XMLHttpRequest()
Maybe in case it helps anyone... one other way to handle difference between cors and network error... can work with chrome or firefox... (not perfect solution though)
var external = 'your url';
if (window.fetch) {
// must be chrome or firefox which have native fetch
fetch(external, {'mode':'no-cors'})
.then(function () {
// external is reachable; but failed due to cors
// fetch will pass though if it's a cors error
})
.catch(function () {
// external is _not_ reachable
});
} else {
// must be non-updated safari or older IE...
// I don't know how to find error type in this case
}
Surprisingly you can do that for images (and maybe there is a similar solution for other particular content types). The trick is that does apparently not regard CORS, therefore statement "url fails to load by XHR && url succeeds to load by img src" implies that the URL works but is CORS blocked.
This definitely does not help in all cases, but in some cases (e.g. utility detecting if you're properly passing CORS) it may do the trick. For example I wanted to fire a a warning in console if my app (separate frontend and backend) is installed with correctly configured URL of backend and wrongly configured CORS on server, so this was perfectly enough for me as I can serve an image to test it on.
function testCors(url) {
var myRequest = new XMLHttpRequest();
myRequest.open('GET', url, true);
myRequest.onreadystatechange = () => {
if (myRequest.readyState !== 4) {
return;
}
if (myRequest.status === 200) {
console.log(url, 'ok');
} else {
var myImage = document.createElement('img');
myImage.onerror = (...args) => {console.log(url, 'other type of error', args);}
myImage.onload = () => {console.log(url, 'image exists but cors blocked');}
myImage.src = url;
console.log(url, 'Image not found');
}
};
myRequest.send();
}
To differentiate a CORS violation from other failed AJAX requests, you can inspect the response headers of a HEAD request using server-side code and pass the results back to your client page. For example, if the AJAX request fails (status 0), you could call this script (let's call it cors.php
) and know for certain if the response headers contain Access-Control-*
headers.
Examples:
cors.php?url=http://ip.jsontest.com
cors.php?url=http://www.google.com
cors.php?url=http://10.0.0.1
returns
HTTP/1.1 200 OK Access-Control-Allow-Origin: *
HTTP/1.1 302 Found
Invalid request
cors.php - Customize as needed
<?php /* cors.php */
$url = $_GET["url"];
if(isset($url)) {
$headers = getHeaders($url);
header("Access-Control-Allow-Origin: *");
if(count($headers) == 0) {
die("Invalid request"); // cURL returns no headers on bad urls
} else {
echo $headers[0]; // echo the HTTP status code
}
// Include any CORS headers
foreach($headers as $header) {
if(strpos($header, "Access-Control") !== false) {
echo " " . $header;
}
}
}
function getHeaders($url, $needle = false) {
$headers = array();
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 4); // Timeout in seconds
curl_setopt($ch, CURLOPT_TIMEOUT, 4); // Timeout in seconds
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_VERBOSE, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD'); // HEAD request only
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use(&$headers) {
array_push($headers, $header);
return strlen($header);
});
curl_exec($ch);
return $headers;
} /* Drakes, 2015 */
Client-side test harness:
function testCORS(url, $elem) {
$.ajax({
url: url,
timeout: 4000
})
.fail(function(jqXHR, textStatus) {
if(jqXHR.status === 0) {
// Determine if this was a CORS violation or not
$.ajax({
context: url,
url: "http://myserver.com/cors.php?url=" + escape(this.url),
})
.done(function(msg) {
if(msg.indexOf("HTTP") < 0) {
$elem.text(url + " - doesn't exist or timed out");
} else if(msg.indexOf("Access-Control-Allow-Origin") >= 0) {
$elem.text(url + " - CORS violation because '" + msg + "'");
} else {
$elem.text(url + " - no Access-Control-Allow-Origin header set");
}
});
} else {
// Some other failure (e.g. 404), but not CORS-related
$elem.text(url + " - failed because '" + responseText + "'");
}
})
.done(function(msg) {
// Successful ajax request
$elem.text(this.url + " - OK");
}); /* Drakes, 2015 */
}
Harness driver:
// Create a div and append the results of the URL calls
$div = $("<div>");
$("body").append($div);
var urls = ["http://ip.jsontest.com", "http://google.com", "http://10.0.0.1"];
urls.map( function(url) {
testCORS(url, $div.append("<h4>").children().last());
});
The results:
http://ip.jsontest.com - OK
http://google.com - no Access-Control-Allow-Origin header set
http://10.0.0.1 - doesn't exist or timed out
Here's what I did - although it does need you to make changes to the remote server (add a page to it).
However, beware that it is very difficult to make it secure if passing parameters either way (anyone can embed your iframe, other parties can cause postMessage to the page or the iframe).
And it isn't perfect (it only detects errors that are not transient one-offs).
However although it is a lot of work you can get more information about the cause of an error. Very useful when you can't directly access the browser of your users.
PS: this is completely different from the PHP answer that suggests your server should talk to the remote server (a) that isn't much use to diagnose communication failure causes, and (b) using curl from PHP is just asking for your server to be seriously pwned!
No, there is no way to tell the difference, according the W3C Spec.
Here's how the CORS specification specifies the simple cross-origin request procedure:
Apply the make a request steps and observe the request rules below while making the request.
If the manual redirect flag is unset and the response has an HTTP status code of 301, 302, 303, 307, or 308: Apply the redirect steps.
If the end user cancels the request: Apply the abort steps.
If there is a network error: In case of DNS errors, TLS negotiation failure, or other type of network errors, apply the network error steps. Do not request any kind of end user interaction...
Otherwise: Perform a resource sharing check. If it returns fail, apply the network error steps...
In the case of either a failed network connection or a failed CORS exchange, the network error steps are applied, so there is literally no way to distinguish between the two cases.
Why? One benefit is that it prevents an attacker from inspecting the network topology of a LAN. For example, a malicious Web page script could find the IP address of your router by requesting its HTTP interface and therefore learn a few things about your network topology (e.g., how big your private IP block is, /8
or /16
). Since your router doesn't (or shouldn't) send CORS headers, the script learns absolutely nothing.