It seems that I am unable to change most request headers from JavaScript when making an AJAX call using XMLHttpRequest. Note that when request.setRequestHeader has to be called after request.open() in Gecko browsers (see http://ajaxpatterns.org/Talk:XMLHttpRequest_Call). When I set the Referer, it doesn't get set (I looked at the request headers sent using Firebug and Tamper Data). When I set User-Agent, it messed up the AJAX call completely. Setting Accept and Content-Type does work, however. Are we prevented from setting Referer and User-Agent in Firefox 3?
var request = new XMLHttpRequest();
var path="http://www.yahoo.com";
request.onreadystatechange=state_change;
request.open("GET", path, true);
request.setRequestHeader("Referer", "http://www.google.com");
//request.setRequestHeader("User-Agent", "Mozilla/5.0");
request.setRequestHeader("Accept","text/plain");
request.setRequestHeader("Content-Type","text/plain");
request.send(null);
function state_change()
{
if (request.readyState==4)
{// 4 = "loaded"
if (request.status==200)
{// 200 = OK
// ...our code here...
alert('ok');
}
else
{
alert("Problem retrieving XML data");
}
}
}
W3C Spec on setrequestheader.
The brief points:
If the request header had
already been set, then the new value
MUST be concatenated to the existing
value using a U+002C COMMA followed by
a U+0020 SPACE for separation.
UAs MAY give the User-Agent header an initial value, but MUST allow authors to append values to it.
However - After searching through the framework XHR in jQuery they don't allow you to change the User-Agent or Referer headers. The closest thing:
// Set header so the called script knows that it's an XMLHttpRequest
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
I'm leaning towards the opinion that what you want to do is being denied by a security policy in FF - if you want to pass some custom Referer type header you could always do:
xhr.setRequestHeader('X-Alt-Referer', 'http://www.google.com');
#gnarf answer is right . wanted to add more information .
Mozilla Bug Reference : https://bugzilla.mozilla.org/show_bug.cgi?id=627942
Terminate these steps if header is a case-insensitive match for one of the following headers:
Accept-Charset
Accept-Encoding
Access-Control-Request-Headers
Access-Control-Request-Method
Connection
Content-Length
Cookie
Cookie2
Date
DNT
Expect
Host
Keep-Alive
Origin
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via
Source : https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
For people looking this up now:
It seems that now setting the User-Agent header is allowed since Firefox 43. See https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name for the current list of forbidden headers.
Related
I'm using an HTTP-request in order to get Google's autocomplete suggestions, here's what inspired me to do so:
You can make a GET request to the following URL:
http://suggestqueries.google.com/complete/search?client=chrome&q=cats
Where "client" param is your browser's name (works with most but you may pass >the same type disregarding what the user is currently on and it will still work).
And "q" param is your search string.
This will return you a list of suggested items which you can then put into a jQuery autocomplete plugin or build your own (you can't just get the whole >google dropdown interface to popup with a single function sadly :) )
(src: Add Google autocomplete to Google search bar?)
I'm using this function to get Google's response text:
function httpGetAsync(theUrl, getGoogle){
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
getGoogle(xmlHttp.responseText);
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
However, when the funtion is executed, CORS seems to prevent me from getting a response: Failed to load http://suggestqueries.google.com/complete/search?client=chrome&q=xx: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.
Is there a way of adding an 'Access-Control-Allow-Origin' header to the resource?
You cannot make a request to another domain from the users browser, unless the source has the Access-Control-Allow-Origin header for your domain. In conclusion, you cannot make the request on the users part without using some kind of proxy, like this one:
https://cors-anywhere.herokuapp.com/
Example:
url: https://cors-anywhere.herokuapp.com/https://google.com
I make AJAX calls to CORS server, I make many attempts to be able to read cookies returned on response using javascript client side, however, in vain .
1. First Attempt :
- Server Side (node.js powered by express) :
response.header('Access-Control-Allow-Origin', '*');
response.header('Access-Control-Allow-Headers', 'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Set-Cookie');
response.header('Access-Control-Expose-Headers', "Set-Cookie");
//------SET COOKIES
response.cookie('SessionId', GeneratorId(64), {
maxAge:3600000,
httpOnly:flase // i disable httpOnly ==> does not work
});
- client Side :
var xhttp=new XMLHttpRequest();
xhttp.open("POST", "http://localhost:9090/api/map", true);
xhttp,send(`{layer:1}`);
2. Second Attempt: (withCredentials)
-Server Side :
//Append another response' header
response.header('Access-Control-Allow-Credentials','true');
-Client Side :
// Before xhttp.send , I add another instruction :
xhttp.withCredentials=true;
3. Third Attempt :
- Server Side :
//Avoid the wildcard on Access-Control-Allow-Origin =>Replace the first header by :
response.header('Access-Control-Allow-Origin', request.get('Origin'));
- Client Side :
// Nothing is appended
Conclusion :
With all those attempts , xhttp.getResponseHeader('Set-Cookie') still returns null even :
Set-Cookie is assigned to the response header : Access-Control-Expose-Headers .
I saw cookies on the browser console (Inspector) :
TL;DR: the Set-Cookie header is entirely off-limits: you can't access it, even when you include it in Access-Control-Expose-Headers. However, once it's set, and the cookie isn't marked httpOnly, you should be able to access it through document.cookie.
TMI follows:
As documented here,
A response will typically get its CORS-exposed header-name list set by parsing the Access-Control-Expose-Headers header. This list is used by a CORS filtered response to determine which headers to expose.
What a CORS filtered response is, is documented here:
A CORS filtered response is a filtered response whose type is "cors", header list excludes any headers in internal response's header list whose name is not a CORS-safelisted response-header name, given internal response's CORS-exposed header-name list, and trailer is empty.
And safelisted headers are subsequently documented here:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
Any value in list that is not a forbidden response-header name.
Finally, the list of forbidden response-header names is listed here:
Set-Cookie
Set-Cookie2
Hi I'm working on connecting to an API that is using Layer 7 as an IP authorizer and eGalaxy as a credentials authorizer, when the curl request is sent a line of xml is sent back to me. I'm currently working on localhost, I've implemented the Access-Control-Allow-Origin chrome extension.
My curl request looks as such:
curl https://client-url/eGalaxy.aspx -H 'Content-Type:text/html' --data '<?xml version:"1.0" encoding="UTF-8"?><Envelope><Header><SourceID>0</SourceID><MessageID>131</MessageID><MessageType>Authenticate</MessageType></Header><Body><Authenticate><Username>*username*</Username><Password>*password*</Password><PasswordEncrypted>NO</PasswordEncrypted></Authenticate></Body></Envelope>' --insecure
When I tried to create an ajax request I receive an "Invalid HTTP status code 500" error and "OPTIONS url" which drops down to show:
n.ajaxTransport.k.cors.a.crossDomain.send # jquery-2.1.3.js:4
n.extend.ajax # jquery-2.1.3.js:4
(anonymous function) # VM947:2
InjectedScript._evaluateOn # VM899:895
InjectedScript._evaluateAndWrap # VM899:828
InjectedScript.evaluate # VM899:694
My ajax code is as follows:
$.ajax({
url:'https://client-url/eGalaxy.aspx',
data:'<?xml version:"1.0" encoding="UTF-8"?><Envelope><Header>
<SourceID>0</SourceID><MessageID>131</MessageID>
<MessageType>Authenticate</MessageType></Header><Body>
<Authenticate><Username>*username*</Username>
<Password>*password*</Password>
<PasswordEncrypted>NO</PasswordEncrypted></Authenticate></Body>
</Envelope>',
type:'POST',
contentType:'text/xml',
dataType:'xml',
success: function(data){
},
error: function(){
}
});
Any help with translating into a proper AJAX request would be appreciated!
EDIT: If this makes a difference these are the headers that are returned with the client's xml when the curl is complete(client information deleted)
This application will be made into a widget as well, so it will not be running off of a hosting site.
UPDATE 1: I'm using #KevinB's suggestion that the CORS headers were still not properly added.
Here is my updated JS code, copied from this link:
var url = 'https://client-url/eGalaxy.aspx';
var data = '<?xml version="1.0" encoding="UTF-8"?><Envelope><Header><SourceID>1</SourceID><MessageID>131</MessageID><MessageType>Authenticate</MessageType></Header><Body><Authenticate><Username>*username*</Username><Password>*password</Password><PasswordEncrypted>NO</PasswordEncrypted></Authenticate></Body></Envelope>';
var xhr = createCORSRequest('POST', url);
xhr.send(data);
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// Check if the XMLHttpRequest object has a "withCredentials" property.
// "withCredentials" only exists on XMLHTTPRequest2 objects.
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// Otherwise, check if XDomainRequest.
// XDomainRequest only exists in IE, and is IE's way of making CORS requests.
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// Otherwise, CORS is not supported by the browser.
xhr = null;
}
return xhr;
}
var xhr = createCORSRequest('GET', url);
if (!xhr) {
throw new Error('CORS not supported');
}
When run with the CORS Chrome extension off I receive an Access-Control-Allow-Origin =! 'null' error. Knowing that CORS needs Access-Control-Allow-Origin header to =! 'null' will this cause problems in the future with making this into a widget that will be put into a Content Manager system?
With it on the origin is set to 'www.evil.com', with the only error in the code being that it says the xhr.send() is an anonymous method. Using breakpoints I can see the xhr in xhr.send() is set to an empty request:
> XMLHttpRequest {response: "", responseText: ""}
Inside the createCORSRequest this line is undefined. I've tested using 'GET' and 'POST' as the method.
xhr.open(method, url, true)
EDIT 2:
Using #Fabiano's approach I've changed the web.config for two versions of what I suspect is my server(?). I'm attaching screenshots of what I've gone through
No luck, so far. Decided to use xhr.AppendHeader:
I decided to use xhr.setRequestHeader("Access-Control-Allow-Origin", "*");
The Network tab Headers for eGalaxy.aspx
There is an error in your XML. You put version:"1.0", and this makes the XML invalid.
Change to version="1.0" and try to make your request. It should work.
This may be the cause for the "Bad request" error.
You can validate your XML here: enter link description here
EDIT: After some research, the problem may be with the headers sent by your server. Your server (or page, .aspx in this case) seems to skip the header you need, the "Access-Control-Allow-Origin: *".
Look at this link: http://enable-cors.org/server.html
This site shows you how to implement it for your server. Since the page you are requesting is called eGalaxy.aspx, then you have 2 ways to implement the headers:
1- Put the line Response.AppendHeader("Access-Control-Allow-Origin", "*"); if the page is a simple ASP.NET application. If it uses Web API 2, you need to implement a different way as it is shown here: http://enable-cors.org/server_aspnet.html
2- Edit the web.config file on the root of your server and add these lines inside the tag:
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
For a ASP.NET application, these are the ways you have. The link I mentioned has solutions for other applications, take a look and choose the right one. :)
Note that the value * tells you that your server will accept any cross-origin request. This may lead to a security issue, so the best you can do is to put your domain address instead of *.
I hope it helps!
I'm developing a Firefox OS client for ownCloud. When I try to login and send the user credentials to the server, I need to obtain in response the cookie that I will use to authenticate in ownCloud in each request.
My problem is that as I’ve seen in Wireshark, the cookie is sent in a HTTP 302 message, but I cannot read this message in my code because Firefox handles it automatically and I read the final HTTP 200 message without cookie information in the
request.reponseText;
request.getAllResponseHeaders();
So my question is if there is any way to read this HTTP 302 message headers, or if I can obtain the cookie from Firefox OS before I send the next request, or even make Firefox OS to add the cookie automatically. I use the following code to make the POST:
request = new XMLHttpRequest({mozSystem: true});
request.open('post', serverInput, true);
request.withCredentials=true;
request.addEventListener('error', onRequestError);
request.setRequestHeader("Cookie",cookie_value);
request.setRequestHeader("Connection","keep-alive");
request.setRequestHeader("Content-type","application/x-www-form-urlencoded");
request.send(send_string);
if(request.status == 200 || request.status==302){
response = request.responseText;
var headers = request.getAllResponseHeaders();
document.getElementById('results').innerHTML="Server found";
loginSuccessfull();
}else{
alert("Response not found");
document.getElementById('results').innerHTML="Server NOT found";
}
"mozAnon
Boolean: Setting this flag to true will cause the browser not to expose the origin and user credentials when fetching resources. Most important, this means that cookies will not be sent unless explicitly added using setRequestHeader.
mozSystem
Boolean: Setting this flag to true allows making cross-site connections without requiring the server to opt-in using CORS. Requires setting mozAnon: true, i.e. this can't be combined with sending cookies or other user credentials." [0]
I'm not sure if you're an owncloud developer, but if you are and have access to the server, you should try setting CORS headers. [1] Maybe if you can stand up a proxy server and have your app connect to the proxy server that does have CORS enabled?
There's also a withCredentials property [2] you can set on instances of xhr objects. It looks like it will add the header Access-Control-Request-Headers: "cookies" and send an HTTP OPTIONS request, which is the preflight [3]. So this would still require server side support for CORS. [4]
Though it seems like this shouldn't work based on internal comments [5], I was able to run this from a simulator and see the request and response headers:
var x = new XMLHttpRequest({ mozSystem: true });
x.open('get', 'http://stackoverflow.com');
x.onload = function () { console.log(x.getResponseHeader('Set-Cookie')); };
x.setRequestHeader('Cookie', 'hello=world;');
x.send();
You'd probably want to reassign document.cookie in the onload event, rather than logging it, if the response header exists (not every site sets cookies on every request). You'd also want to set the request header to document.cookie itself.
[0] https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#XMLHttpRequest%28%29
[1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
[2] https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Properties
[3] https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests
[4] http://www.html5rocks.com/en/tutorials/cors/#toc-making-a-cors-request
[5] https://bugzilla.mozilla.org/show_bug.cgi?id=966216#c2
this is driving me nutters.
jQuery 1.4.2, windows XP sp3
Here is my test.
Load firefox 3.5+
http://plungjan.name/test/testcors.html
works
Save the file to harddisk and run from there
From my office the external works and the internal does not
What is also interesting is that I cannot run both in one go.
Background:
I do a GET to an internal web service that uses CORS.
Please do NOT post any answers about FF not handling cross domain request when it does since v3.5 as detailed here and here
It works in IE8 and FF3.6.6 from one server to the other and now almost from file system (file:///) to service.
Only from file system and only when FF 3.6.6 needs to negotiate (the user is already logged in, authorised and sends the credentials!) do I not get the data after negotiation. jQuery xhr returns status 0 and no data/responseText or whatever
Seems to me, jQuery reacts and saves the xhr from the 401 rather than from the 200 OK later
Here is the result I get at the end of the communication when I alert the XHR object:
Status:success
Data:[]
XHR:
some native functions,
readyState:4
status:0
responseXML:null
responseText:
withCredentials:true
if I make a call to the same server but without needing credentials, the data is returned just fine cross domain
So the communication is as follows:
GET /restapplicationusingcors/authenticationneeded-internal/someid
Accept: application/json
Accept-Language: en
.
.
Origin: null
Cookie: LtpaToken=...
the return is
HTTP/1.1 401 Unauthorized
Server: Apache
Pragma: No-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 01:00:00 CET
WWW-Authenticate: Negotiate
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html
Then FF sends
GET /restapplicationusingcors/authenticationneeded-internal/someid HTTP/1.1
Host: myhost.myintranet.bla
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.6) Gecko/20100625 Firefox/3.6.6
Accept: application/json
Accept-Language: en
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Origin: null
Cookie: LtpaToken=....
Authorization: Negotiate ....
and is rewarded with the file I need, but cannot get at in FF:
HTTP/1.1 200 OK
Date: Tue, 20 Jul 2010 12:08:39 GMT
Pragma: No-cache
Cache-Control: no-cache, max-age=600, s-maxage=3600
Expires: Thu, 01 Jan 1970 01:00:00 CET
X-Powered-By: ...
Content-Disposition: inline;filename=nnnnnn.json
Content-Language: en
Access-Control-Allow-Origin: ...
Keep-Alive: timeout=6, max=70
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json;charset=UTF-8
THE DATA SENT FROM THE SERVER IS NOT IN THE XHR OBJECT
Here is my code
function getJSON(url,func,lang) {
accept = 'application/json';
lang=lang?lang:"*";
// gruesome hack to handle that APPENDS the mime header to */* !!!
// NOW HANDLED by first setting Accept to "" !!!
// if ($.browser.msie && url.indexOf('serveAsMime')==-1) {
// url+= '?serveAsMime='+accept;
// }
if (currentRequest != null) currentRequest.abort();
var requestObjectJSON = {
url : url,
// dataType: "json",
method : 'get',
beforeSend: function(xhr){
xhr.setRequestHeader('Accept', ""); // IE hack
xhr.setRequestHeader('Accept', accept);
xhr.setRequestHeader('Accept-Language', lang);
if (url.indexOf('-internal') !=-1) {
try {
xhr.withCredentials = true;
alert('set credentials')
}
catch(e) {
alert('cannot set xhr with credentials')
}
}
},
success: function(data,status,xhr) {
var responseText = xhr.responseText;
var responseJSON = xhr.responseJSON;
var t = "";
try{
for (var o in xhr) t += '\n'+o+':'+xhr[o];
}
catch(e) {
if (e.message.indexOf('.channel')==-1)alert(e.message);
}
alert('Status:'+status+'\nData:['+data+']\nXHR:'+t);
func(responseText);
},
}
currentRequest = $.ajax(requestObjectJSON);
}
This is a stab in the dark since I don't fully understand your problem, but I think you might be having a problem with file: URLs, which are not treated as having any origin. I'm not sure it's even possible to authorize CORS from a file URL.
So you need to set an ajax prefilter in your model/collection in order to use CORS. Otherwise it doesn't send the cookie.
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
options.xhrFields = {
withCredentials: true
};
});
I put this in my Model/Collection initialize function.
These are the conditions to be met to make CORS working with secured services:
Service response should contain header Access-Control-Allow-Credentials: true (see Requests with credentials and Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true).
Service response header Access-Control-Allow-Origin should not be *. The idea is to return the value passed by client in header Origin (see examples in this post).
According to specification, OPTIONS method should return HTTP code 200, thus it cannot be secured (see The CORS).
For methods PUT/POST that need to pass certain request headers to service (like Content-Type or Accept), these headers need to be listed in Access-Control-Allow-Headers (see jQuery AJAX fails to work when headers are specified)
JavaScript should set this XMLHttpRequest property: xhr.withCredentials = true; (as answered by Kirby)
Altogether configuration for Apache:
# Static content:
SetEnvIf Request_URI ".*" no-jk
# RESTful service:
SetEnvIf Request_URI "^/backend/" !no-jk
SetEnvIf Request_Method "OPTIONS" no-jk
# Fallback value:
SetEnv http_origin "*"
SetEnvIf Origin "^https?://(localhost|.*\.myconpany\.org)(:[0-9]+)?$" http_origin=$0
Header set Access-Control-Allow-Credentials "true"
Header set Access-Control-Allow-Origin "%{http_origin}e"
Header set Access-Control-Allow-Methods "GET,POST,PUT,DELETE"
Header set Access-Control-Allow-Headers "Content-Type, Accept"
JkMount /* loadbalancer
CORS with file://
If you have problems by allowing origins from the file:// protocol, according to The Web Origin Concept it should be done the same way as any other origins. I could not find information about the browser support, but I think every browser which is supporting CORS does support this one either.
The Web Origin Concept tells us the following about the file URI scheme:
4. If uri-scheme is "file", the implementation MAY return an
implementation-defined value.
NOTE: Historically, user agents have granted content from the
file scheme a tremendous amount of privilege. However,
granting all local files such wide privileges can lead to
privilege escalation attacks. Some user agents have had
success granting local files directory-based privileges, but
this approach has not been widely adopted. Other user agents
use globally unique identifiers for each file URI, which is
the most secure option.
According to wikipedia the domain by the file URI scheme is localhost. It is omittable by the address bar, but I don't think it is omittable in the allow origin headers. So if your browser implementation allows origin with a file URI scheme, then you should add file://localhost to your allowed origins, and everything should work properly after that.
This was how it should work, now meet reality:
I tested with current firefox 29.0.1, and it did not work. However the file:// protocol is transformed into null origin by this implementation. So by firefox the null works. I tried with a wider domain list, but I did not manage to allow multiple domains. It seems like firefox does not support a list with multiple domains currently.
I tested with chrome 35.0.1916, it works the same way as firefox did.
I tested with msie 11.0.9600. By request from the file protocol it always shows an allow blocked content button, even by not allowing the null origin. By other domains it works the same way as the previous browsers.
HTTP basic auth:
The credentials part I tried out with PHP and HTTP basic auth.
http://test.loc
Displays :-) when logged in and :-( when unauthorized.
<?php
function authorized()
{
if (empty($_SERVER['PHP_AUTH_USER']) || empty($_SERVER['PHP_AUTH_PW']))
return false;
return ($_SERVER['PHP_AUTH_USER'] == 'username' && $_SERVER['PHP_AUTH_PW'] == 'password');
}
function unauthorized()
{
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Restricted Area"');
echo ':-(';
}
if (!isset($_GET['logout']) && authorized()) {
echo ':-)';
} else
unauthorized();
So this code changes the location by login and logout.
Cross domain CORS with HTTP basic auth
http://todo.loc
Gets the content of http://test.loc with cross domain XHR and displays it.
cross domain ajax<br />
<script>
var xhr = new XMLHttpRequest();
xhr.open('GET', "http://test.loc", true);
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState==4) {
document.body.innerHTML += xhr.responseText;
}
};
xhr.send();
</script>
Requires headers by http://test.loc:
Access-Control-Allow-Origin: http://todo.loc
Access-Control-Allow-Credentials: true
Cross scheme CORS with HTTP basic auth
file:///path/x.html
Gets the content of http://test.loc with cross scheme XHR and displays it.
cross scheme ajax<br />
<script>
var xhr = new XMLHttpRequest();
xhr.open('GET', "http://test.loc", true);
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState==4) {
document.body.innerHTML += xhr.responseText;
}
};
xhr.send();
</script>
Requires headers by http://test.loc:
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
Conclusion:
I tested cross-sheme CORS with credentials called from file:// and it works pretty well in firefox, chrome and msie.