CORS Request - Terminal Works, Browser Fails - javascript

I'm trying to fetch an image via ajax from an amazon server. That server is configured to return the Access-Control-Allow-Origin: * header.
The issue is that when I do the request from terminal, I see the response fine.
But when I do the AJAX request from browser, I'm blocked and I don't see that header on the response.
Terminal request:
➜ ~ curl -sI -H "Origin: google.com" -H "Access-Control-Request-Method: GET" http://my-domain/my-image.jpg
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 595284
Connection: close
Date: Thu, 13 Aug 2015 12:28:02 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST
Access-Control-Max-Age: 3000
Content-Disposition: attachment; filename="Hydrangeas.jpg"
Last-Modified: Thu, 13 Aug 2015 07:00:21 GMT
x-amz-version-id: null
ETag: "bdf3bf1da3405725be763540d6601144"
Server: AmazonS3
Age: 4132
X-Cache: Hit from cloudfront
Via: 1.1 b7b782c917cd62c6605a1684c071a774.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 7t1gB74LHHWEIXcl_ml6pWC9voDrfSakCMUJ2KeAjMYZ-37aZYmPcg==
When I try from Chrome/FF (haven't tried others), I see the canonical error message and when I look in the network tab I don't see the ACAA header:
XMLHttpRequest cannot load ***. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '***' is therefore not allowed access.
This is the code I use to try and get the image:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState === 4 && this.status === 200){
// never gets here, status is always 0 from the block
}
}
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();

Related

Why is event.total in XMLHttpRequest always 0, unless Content-Type is PDF?

I tried various days to make a progress bar for my download, a task which I expected to be quite trivial.
Everything seemed to to be just right, but the event.total-value was always 0 (or undefined in the angular-version.
This is basically how the file got passed:
<?php // backend.php
if (isset($_GET["file"])) {
$file = "dlstuff/{$_GET["file"]}";
}
$stream = fopen($file, 'rb');
header('Content-Length: '. filesize($file));
header("Content-Type: text/plain", true);
fpassthru($stream);
And the header was exposed in .htaccess as stated here:
Header add Access-Control-Expose-Headers "Content-Length"
I obtained the file with Javascript like this:
The application was written in angular, but I could reproduce it with simple plain HTML:
const oReq = new XMLHttpRequest();
oReq.addEventListener("progress", oEvent => {
console.log("progress");
console.log({ loaded: oEvent.loaded, total: oEvent.total, status: oReq.status });
console.log(oReq.getAllResponseHeaders());
});
oReq.addEventListener("load", () => {
console.log("complete");
console.log({ status: oReq.status, downloaded: oReq.response.length });
console.log(oReq.getAllResponseHeaders());
});
oReq.open("GET", "http://localhost/backend.php?file=whatever.htm");
oReq.send();
I read a lot of stuff
XMLHttpRequest "total" returns 0 when fetching JSON
How can I access the Content-Length header from a cross domain Ajax request?
Content-length and other HTTP headers?
Content-Length header with HEAD requests?
But still - no event.total and the Content-Length was not visible in the browser-console.
progress
(index):11 {loaded: 65536, total: 0, status: 200}
(index):12 access-control-expose-headers: Content-Length
connection: Keep-Alive
content-encoding: gzip
content-type: text/plain;charset=UTF-8
date: Mon, 13 Sep 2021 15:09:22 GMT
keep-alive: timeout=5, max=99
server: Apache/2.4.38 (Debian)
transfer-encoding: chunked
vary: Accept-Encoding
I started to loose my mind overt this until I changed the Content-Type-header in the PHP-Script to application/pdf by accident. Suddenly it worked!
What it going on? The real application may have various file formats but I would like to treat them all as text from the Javascript side.
--
Edit 1:
`application/octet-stream``instead of Pdf works as well. But the question remains: why?

Why is it not possible to access the Content-Length response header even when I can access the actual contents of the file? (for a CORS request)

In JavaScript, the Fetch API's access to certain headers is restricted for security reasons during a CORS request.
We can see the actual headers of a resource using curl in the terminal:
curl -I https://i.imgur.com/z4d4kWk.jpg
And here's the result:
HTTP/1.1 200 OK
Last-Modified: Thu, 02 Feb 2017 11:15:53 GMT
ETag: "18c50e1bbe972bdf9c7d9b8f6f019959"
x-amz-storage-class: STANDARD_IA
Content-Type: image/jpeg
cache-control: public, max-age=31536000
Content-Length: 146515
Accept-Ranges: bytes
Date: Sat, 14 Jul 2018 01:34:10 GMT
Age: 446795
Connection: keep-alive
X-Served-By: cache-iad2134-IAD, cache-syd18935-SYD
X-Cache: HIT, HIT
X-Cache-Hits: 1, 1
X-Timer: S1531532051.675972,VS0,VE2
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Origin: *
Server: cat factory 1.0
Here's the equivalent request using fetch:
await fetch("https://i.imgur.com/z4d4kWk.jpg", { method: 'HEAD' }).then(r => [...r.headers.entries()].map(l => l.join(": ")).join("\n"))
And here's the result:
cache-control: public, max-age=31536000
content-type: image/jpeg
last-modified: Thu, 02 Feb 2017 11:15:53 GMT
But imgur allows other origins to access that resource (see Access-Control-Allow-Origin: * in curl output above), and so we can actually get the contents of the file:
await fetch("https://i.imgur.com/z4d4kWk.jpg").then(r => r.arrayBuffer())
So we can get the actual contents of the file, and yet we can't get the headers?
At first I thought it was because we were making a HEAD request and imgur specifies Access-Control-Allow-Methods: GET, OPTIONS, but I tried using a GET request and still got the same limited set of headers:
await fetch("https://i.imgur.com/z4d4kWk.jpg").then(r => [...r.headers.entries()].map(l => l.join(": ")).join("\n"))
Any idea why we're unable to see the headers even when we have full access to the content itself?

CORS issue? - Response for preflight has invalid HTTP status code 405

I am facing the frequent 405 problem when launching a POST request from my localhost:8080 to an external API. I've tried to use many different options but I don´t get the key of the problem.
URL = api end point; //Your URL
var datajson = '{'
+'"image" : "'+imageBase64+'"'
+'}';
var req = new XMLHttpRequest();
var body = JSON.parse(datajson)
if ('withCredentials' in req) {
req.open('POST', URL, true);
req.setRequestHeader('Content-Type', 'application/json');
req.onreadystatechange = handleResponse(req);
req.send(body);
}
The error I get in browser is:
OPTIONS http://blablabla 405 (Method Not Allowed)
sendToBioFace # myjavascript.js:38
send # myjavascript.js:56
onclick # (index):13
(index):1 Failed to load http://blablabla: Response for preflight has invalid HTTP status code 405
Find below the Headers:
Request URL:http://blablabla.com
Request Method:OPTIONS
Status Code:405 Method Not Allowed
Remote Address: destinationip:16111
Referrer Policy:no-referrer-when-downgrade
Response Headers
Access-Control-Allow-Headers:X-Requested-With,Content-Type
Access-Control-Allow-Origin:*
Access-Control-Request-Method:POST,GET,PUT,DELETE,OPTIONS
Allow:POST
Content-Length:1569
Content-Type:text/html; charset=UTF-8
Date:Sun, 28 Jan 2018 19:13:42 GMT
Server:Microsoft-HTTPAPI/2.0
Request Headers
Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language:es-ES,es;q=0.9,en;q=0.8
Access-Control-Request-Headers:content-type
Access-Control-Request-Method:POST
Cache-Control:no-cache
Connection:keep-alive
Host: destinationip:16111
Origin:http://localhost:8080
Pragma:no-cache
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36
You are trying to send cross domain request but the headers that you sent doesn't include OPTIONS.
You should add OPTIONS request header to your outgoing requests.

Receive OAuth2 Token from XHR Request

I am trying to get an OAuth2 Token via XHR-Request in TypeScript like mentioned on this side(https://developer.paypal.com/docs/integration/direct/make-your-first-call/). My code so far:
var clientID = <clientID>;
var secret = <mySecret>;
var oReq = new XMLHttpRequest();
oReq.open("POST", "https://api.sandbox.paypal.com/v1/oauth2/token", true);
oReq.setRequestHeader('grant_type', "client_credentials");
oReq.setRequestHeader('Username', this.clientID);
oReq.setRequestHeader('Password', this.secret);
oReq.setRequestHeader('Content-type', "application/x-www-form-urlencoded");
oReq.setRequestHeader('Accept', "application/json");
oReq.setRequestHeader('Accept-Language', "en_US");
oReq.onreadystatechange = function () {
if (oReq.readyState === 4) {
if (oReq.status === 200) {
console.log(oReq.responseText);
} else {
console.log("Error: " + oReq.status);
}
}
console.log("Status: " + oReq.status);
};
console.log("Status: " + oReq.status);
oReq.send();
Sadly I keep getting 401 as response. I already tried the curl command with the same clientID and secret, which worked for me. Can someone tell me whats wrong with me code?
The request you’re getting the 401 response for isn’t your POST request but instead the CORS preflight OPTIONS request that the browser automatically does on its own.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests has details about what triggers browsers to do a preflight but what it comes down to in this specific case is that you’re adding grant_type, Username, and Password request headers to the request in order to do the authentication needed—but your browser makes the preflight OPTIONS request without those headers (because the purpose of the preflight is to ask the server to indicate whether it’s OK with receiving requests that include those request headers).
And so what happens is the following (reproduced using curl just for illustration purposes):
$ curl -X OPTIONS -i -H "Origin: http://example.com" \
'https://api.sandbox.paypal.com/v1/oauth2/token'
HTTP/1.1 401 Unauthorized
Date: Wed, 09 Aug 2017 09:08:32 GMT
Server: Apache
paypal-debug-id: f8963606c5654
Access-Control-Allow-Origin: http://example.com
Access-Control-Expose-Headers: False
HTTP_X_PP_AZ_LOCATOR: sandbox.slc
Paypal-Debug-Id: f8963606c5654
Set-Cookie: X-PP-SILOVER=name%3DSANDBOX3.API.1%26silo_version%3D1880%26app%3Dapiplatformproxyserv%26TIME%3D282167897%26HTTP_X_PP_AZ_LOCATOR%3Dsandbox.slc; Expires=Wed, 09 Aug 2017 09:38:32 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Content-Length: 0
Connection: close
Content-Type: text/plain; charset=ISO-8859-1
That is, the https://api.sandbox.paypal.com/v1/oauth2/token endpoint apparently requires authentication even for OPTIONS requests. Because it does, there’s no way you can make POST requests to that endpoint directly from your frontend JavaScript code running in a browser.
So you’ll instead need to make the request from your backend code.

XMLHttpRequest cannot load URL. No 'Access-Control-Allow-Origin' header is present on the requested resource

Here is what I have done and understood so far:
My Webpage run from http://localhost:80, I need to access some API calls at http://localhost:8080.
I require to enable CORS, as its a POST and Content-Type is application/json.
So I implemented OPTIONS method on the server and set all the required headers.
Here is the OPTIONS request which the browser makes
Request :
OPTIONS /0/VERIFICATION HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Access-Control-Request-Method: POST
Origin: http://localhost
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36
Access-Control-Request-Headers: x-key, x-signature, content-type
Accept: */*
Referer: http://localhost/jquery.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Response :
HTTP/1.1 200 OK
Allow: POST,GET,PUT,DELETE,OPTIONS
Access-Control-Allow-Origin: *
Accept: */*
Access-Control-Request-Headers: X-Key,X-Signature,Content-Type
Access-Control-Allow-Headers: X-Key,X-Signature,Content-Type
Content-Type: application/json; charset=UTF-8
Content-Length: 0
So I can see all the appropriate headers being set and all looks good until the actual request is made which fails with the following error
Error in Chrome Console
XMLHttpRequest cannot load http://localhost:8080/0/VERIFICATION. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
Here is my javascript code which is making the actual request
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
xhr.withCredentials = false;
if ("withCredentials" in xhr) {
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
function makeCorsRequest() {
var url = "http://localhost:8080/0/VERIFICATION";
var xhr = createCORSRequest('POST', url);
xhr.onload = function() {
var text = xhr.responseText;
alert('Response from CORS request to ' + url + ': ' + title);
};
xhr.onerror = function() {
alert('Woops, there was an error making the request.');
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader("X-Signature", "abcd");
xhr.setRequestHeader("X-Key", "key1");
xhr.send("{json data}");
}
I am using Chrome browser to test the above scenario, my server is a netty based server and html pages are being served via apache.
Update added .htaccess based on the comment below and still the same error.
curl -v 'http://localhost/jquery.html'
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET /jquery.html HTTP/1.1
> User-Agent: curl/7.36.0
> Host: localhost
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Fri, 18 Jul 2014 07:03:22 GMT
* Server Apache/2.4.9 (Unix) PHP/5.5.9 is not blacklisted
< Server: Apache/2.4.9 (Unix) PHP/5.5.9
< Last-Modified: Sun, 30 Mar 2014 06:31:53 GMT
< ETag: "3af-4f5cd17308840"
< Accept-Ranges: bytes
< Content-Length: 943
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Headers: origin, x-requested-with, x-http-method-override, content- type
< Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS
< Content-Type: text/html
You need to set the headers when you serve the html pages, too. .htaccess for Apache:
Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, x-http-method-override, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
None of the browser, curl or wireshark could tell what was going wrong. The problem was very was lame, I did the following on the server :
response.addHeader(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_HEADERS, "X-Key,X-Signature,Content-Type");
The correct way is :
response.addHeader(HttpHeaders.Names.ACCESS_CONTROL_REQUEST_HEADERS, "X-Key");
response.addHeader(HttpHeaders.Names.ACCESS_CONTROL_REQUEST_HEADERS, "X-Signature");
response.addHeader(HttpHeaders.Names.ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");

Categories

Resources