mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
Bug 608735. Fix getAllResponseHeaders for cross-origin requests to actually expose the headers CORS says it can expose. r=bzbarsky
This commit is contained in:
parent
382d12416e
commit
dffc5ee5ab
@ -1336,6 +1336,64 @@ nsXMLHttpRequest::SlowAbort()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*Method that checks if it is safe to expose a header value to the client.
|
||||
It is used to check what headers are exposed for CORS requests.*/
|
||||
bool
|
||||
nsXMLHttpRequest::IsSafeHeader(const nsACString& header, nsIHttpChannel* httpChannel)
|
||||
{
|
||||
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
|
||||
if (!nsContentUtils::IsCallerChrome() &&
|
||||
(header.LowerCaseEqualsASCII("set-cookie") ||
|
||||
header.LowerCaseEqualsASCII("set-cookie2"))) {
|
||||
NS_WARNING("blocked access to response header");
|
||||
return false;
|
||||
}
|
||||
// if this is not a CORS call all headers are safe
|
||||
if (!(mState & XML_HTTP_REQUEST_USE_XSITE_AC)){
|
||||
return true;
|
||||
}
|
||||
// Check for dangerous headers
|
||||
// Make sure we don't leak header information from denied cross-site
|
||||
// requests.
|
||||
if (mChannel) {
|
||||
nsresult status;
|
||||
mChannel->GetStatus(&status);
|
||||
if (NS_FAILED(status)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const char* kCrossOriginSafeHeaders[] = {
|
||||
"cache-control", "content-language", "content-type", "expires",
|
||||
"last-modified", "pragma"
|
||||
};
|
||||
for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
|
||||
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
nsAutoCString headerVal;
|
||||
// The "Access-Control-Expose-Headers" header contains a comma separated
|
||||
// list of method names.
|
||||
httpChannel->
|
||||
GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
|
||||
headerVal);
|
||||
nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
|
||||
bool isSafe = false;
|
||||
while (exposeTokens.hasMoreTokens()) {
|
||||
const nsDependentCSubstring& token = exposeTokens.nextToken();
|
||||
if (token.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!IsValidHTTPToken(token)) {
|
||||
return false;
|
||||
}
|
||||
if (header.Equals(token, nsCaseInsensitiveCStringComparator())) {
|
||||
isSafe = true;
|
||||
}
|
||||
}
|
||||
return isSafe;
|
||||
}
|
||||
|
||||
/* DOMString getAllResponseHeaders(); */
|
||||
IMPL_STRING_GETTER(GetAllResponseHeaders)
|
||||
void
|
||||
@ -1350,12 +1408,8 @@ nsXMLHttpRequest::GetAllResponseHeaders(nsString& aResponseHeaders)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel()) {
|
||||
nsRefPtr<nsHeaderVisitor> visitor = new nsHeaderVisitor();
|
||||
nsRefPtr<nsHeaderVisitor> visitor = new nsHeaderVisitor(this, httpChannel);
|
||||
if (NS_SUCCEEDED(httpChannel->VisitResponseHeaders(visitor))) {
|
||||
CopyASCIItoUTF16(visitor->Headers(), aResponseHeaders);
|
||||
}
|
||||
@ -1448,64 +1502,9 @@ nsXMLHttpRequest::GetResponseHeader(const nsACString& header,
|
||||
return;
|
||||
}
|
||||
|
||||
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
|
||||
if (!nsContentUtils::IsCallerChrome() &&
|
||||
(header.LowerCaseEqualsASCII("set-cookie") ||
|
||||
header.LowerCaseEqualsASCII("set-cookie2"))) {
|
||||
NS_WARNING("blocked access to response header");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for dangerous headers
|
||||
if (mState & XML_HTTP_REQUEST_USE_XSITE_AC) {
|
||||
// Make sure we don't leak header information from denied cross-site
|
||||
// requests.
|
||||
if (mChannel) {
|
||||
nsresult status;
|
||||
mChannel->GetStatus(&status);
|
||||
if (NS_FAILED(status)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const char *kCrossOriginSafeHeaders[] = {
|
||||
"cache-control", "content-language", "content-type", "expires",
|
||||
"last-modified", "pragma"
|
||||
};
|
||||
bool safeHeader = false;
|
||||
uint32_t i;
|
||||
for (i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
|
||||
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
|
||||
safeHeader = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!safeHeader) {
|
||||
nsAutoCString headerVal;
|
||||
// The "Access-Control-Expose-Headers" header contains a comma separated
|
||||
// list of method names.
|
||||
httpChannel->
|
||||
GetResponseHeader(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"),
|
||||
headerVal);
|
||||
nsCCharSeparatedTokenizer exposeTokens(headerVal, ',');
|
||||
while(exposeTokens.hasMoreTokens()) {
|
||||
const nsDependentCSubstring& token = exposeTokens.nextToken();
|
||||
if (token.IsEmpty()) {
|
||||
continue;
|
||||
}
|
||||
if (!IsValidHTTPToken(token)) {
|
||||
return;
|
||||
}
|
||||
if (header.Equals(token, nsCaseInsensitiveCStringComparator())) {
|
||||
safeHeader = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!safeHeader) {
|
||||
return;
|
||||
}
|
||||
if (!IsSafeHeader(header, httpChannel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
aRv = httpChannel->GetResponseHeader(header, _retval);
|
||||
@ -3989,18 +3988,13 @@ NS_IMPL_ISUPPORTS1(nsXMLHttpRequest::nsHeaderVisitor, nsIHttpHeaderVisitor)
|
||||
NS_IMETHODIMP nsXMLHttpRequest::
|
||||
nsHeaderVisitor::VisitHeader(const nsACString &header, const nsACString &value)
|
||||
{
|
||||
// See bug #380418. Hide "Set-Cookie" headers from non-chrome scripts.
|
||||
if (!nsContentUtils::IsCallerChrome() &&
|
||||
(header.LowerCaseEqualsASCII("set-cookie") ||
|
||||
header.LowerCaseEqualsASCII("set-cookie2"))) {
|
||||
NS_WARNING("blocked access to response header");
|
||||
} else {
|
||||
mHeaders.Append(header);
|
||||
mHeaders.Append(": ");
|
||||
mHeaders.Append(value);
|
||||
mHeaders.Append("\r\n");
|
||||
}
|
||||
return NS_OK;
|
||||
if (mXHR->IsSafeHeader(header, mHttpChannel)) {
|
||||
mHeaders.Append(header);
|
||||
mHeaders.Append(": ");
|
||||
mHeaders.Append(value);
|
||||
mHeaders.Append("\r\n");
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// DOM event class to handle progress notifications
|
||||
|
@ -417,6 +417,7 @@ public:
|
||||
}
|
||||
}
|
||||
void GetAllResponseHeaders(nsString& aResponseHeaders);
|
||||
bool IsSafeHeader(const nsACString& aHeaderName, nsIHttpChannel* aHttpChannel);
|
||||
void OverrideMimeType(const nsAString& aMimeType)
|
||||
{
|
||||
// XXX Should we do some validation here?
|
||||
@ -552,11 +553,14 @@ protected:
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIHTTPHEADERVISITOR
|
||||
nsHeaderVisitor() { }
|
||||
nsHeaderVisitor(nsXMLHttpRequest* aXMLHttpRequest, nsIHttpChannel* aHttpChannel)
|
||||
: mXHR(aXMLHttpRequest), mHttpChannel(aHttpChannel) {}
|
||||
virtual ~nsHeaderVisitor() {}
|
||||
const nsACString &Headers() { return mHeaders; }
|
||||
private:
|
||||
nsCString mHeaders;
|
||||
nsXMLHttpRequest* mXHR;
|
||||
nsCOMPtr<nsIHttpChannel> mHttpChannel;
|
||||
};
|
||||
|
||||
// The bytes of our response body. Only used for DEFAULT, ARRAYBUFFER and
|
||||
|
@ -8,6 +8,10 @@
|
||||
<html>
|
||||
<head>
|
||||
<script>
|
||||
function trimString(stringValue) {
|
||||
return stringValue.replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
|
||||
window.addEventListener("message", function(e) {
|
||||
|
||||
sendData = null;
|
||||
@ -61,7 +65,16 @@ window.addEventListener("message", function(e) {
|
||||
res.responseHeaders[responseHeader] =
|
||||
xhr.getResponseHeader(responseHeader);
|
||||
}
|
||||
|
||||
res.allResponseHeaders = {};
|
||||
var splitHeaders = xhr.getAllResponseHeaders().split("\r\n");
|
||||
for (var i = 0; i < splitHeaders.length; i++) {
|
||||
var headerValuePair = splitHeaders[i].split(":");
|
||||
if(headerValuePair[1] != null) {
|
||||
var headerName = trimString(headerValuePair[0]);
|
||||
var headerValue = trimString(headerValuePair[1]);
|
||||
res.allResponseHeaders[headerName] = headerValue;
|
||||
}
|
||||
}
|
||||
post(e, res);
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,16 @@ window.addEventListener("message", function(e) {\n\
|
||||
res.responseHeaders[responseHeader] =\n\
|
||||
xhr.getResponseHeader(responseHeader);\n\
|
||||
}\n\
|
||||
\n\
|
||||
res.allResponseHeaders = {};\n\
|
||||
var splitHeaders = xhr.getAllResponseHeaders().split("\\r\\n");\n\
|
||||
for (var i = 0; i < splitHeaders.length; i++) {\n\
|
||||
var headerValuePair = splitHeaders[i].split(":");\n\
|
||||
if(headerValuePair[1] != null){\n\
|
||||
var headerName = trimString(headerValuePair[0]);\n\
|
||||
var headerValue = trimString(headerValuePair[1]); \n\
|
||||
res.allResponseHeaders[headerName] = headerValue;\n\
|
||||
}\n\
|
||||
}\n\
|
||||
post(e, res);\n\
|
||||
}\n\
|
||||
\n\
|
||||
@ -74,6 +83,9 @@ window.addEventListener("message", function(e) {\n\
|
||||
function post(e, res) {\n\
|
||||
e.source.postMessage(res.toSource(), "*");\n\
|
||||
}\n\
|
||||
function trimString(stringValue) {\n\
|
||||
return stringValue.replace("/^\s+|\s+$/g","");\n\
|
||||
};\n\
|
||||
\n\
|
||||
</script>\n\
|
||||
</head>\n\
|
||||
|
@ -559,7 +559,16 @@ function runTest() {
|
||||
exposeHeaders: " , ,,y-my-header,z-my-header, ",
|
||||
expectedResponseHeaders: ["y-my-header"],
|
||||
},
|
||||
|
||||
{ pass: 1,
|
||||
method: "GET",
|
||||
responseHeaders: { "Cache-Control": "cacheControl header",
|
||||
"Content-Language": "contentLanguage header",
|
||||
"Expires":"expires header",
|
||||
"Last-Modified":"lastModified header",
|
||||
"Pragma":"pragma header",
|
||||
"Unexpected":"unexpected header" },
|
||||
expectedResponseHeaders: ["Cache-Control","Content-Language","Content-Type","Expires","Last-Modified","Pragma"],
|
||||
},
|
||||
// Check that sending a body in the OPTIONS response works
|
||||
{ pass: 1,
|
||||
method: "DELETE",
|
||||
@ -673,13 +682,19 @@ function runTest() {
|
||||
for (header in test.responseHeaders) {
|
||||
if (test.expectedResponseHeaders.indexOf(header) == -1) {
|
||||
is(res.responseHeaders[header], null,
|
||||
"wrong response header (" + header + ") in test for " +
|
||||
"|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
|
||||
test.toSource());
|
||||
is(res.allResponseHeaders[header], null,
|
||||
"|xhr.getAllResponseHeaderss()|wrong response header (" + header + ") in test for " +
|
||||
test.toSource());
|
||||
}
|
||||
else {
|
||||
is(res.responseHeaders[header], test.responseHeaders[header],
|
||||
"wrong response header (" + header + ") in test for " +
|
||||
"|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
|
||||
test.toSource());
|
||||
is(res.allResponseHeaders[header], test.responseHeaders[header],
|
||||
"|xhr.getAllResponseHeaderss()|wrong response header (" + header + ") in test for " +
|
||||
test.toSource());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user