Bug 1285036 - Part 7: Change SetRequestHeader() and related header code to follow the spec more closely. r=baku
This commit is contained in:
@@ -7071,9 +7071,8 @@ nsContentUtils::IsForbiddenSystemRequestHeader(const nsACString& aHeader)
|
|||||||
static const char *kInvalidHeaders[] = {
|
static const char *kInvalidHeaders[] = {
|
||||||
"accept-charset", "accept-encoding", "access-control-request-headers",
|
"accept-charset", "accept-encoding", "access-control-request-headers",
|
||||||
"access-control-request-method", "connection", "content-length",
|
"access-control-request-method", "connection", "content-length",
|
||||||
"cookie", "cookie2", "content-transfer-encoding", "date", "dnt",
|
"cookie", "cookie2", "date", "dnt", "expect", "host", "keep-alive",
|
||||||
"expect", "host", "keep-alive", "origin", "referer", "te", "trailer",
|
"origin", "referer", "te", "trailer", "transfer-encoding", "upgrade", "via"
|
||||||
"transfer-encoding", "upgrade", "via"
|
|
||||||
};
|
};
|
||||||
for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) {
|
for (uint32_t i = 0; i < ArrayLength(kInvalidHeaders); ++i) {
|
||||||
if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) {
|
if (aHeader.LowerCaseEqualsASCII(kInvalidHeaders[i])) {
|
||||||
|
|||||||
@@ -1465,7 +1465,7 @@ XMLHttpRequestMainThread::Open(const nsACString& inMethod, const nsACString& url
|
|||||||
|
|
||||||
// Clear our record of previously set headers so future header set
|
// Clear our record of previously set headers so future header set
|
||||||
// operations will merge/override correctly.
|
// operations will merge/override correctly.
|
||||||
mAlreadySetHeaders.Clear();
|
mAuthorRequestHeaders.Clear();
|
||||||
|
|
||||||
// When we are called from JS we can find the load group for the page,
|
// When we are called from JS we can find the load group for the page,
|
||||||
// and add ourselves to it. This way any pending requests
|
// and add ourselves to it. This way any pending requests
|
||||||
@@ -2480,6 +2480,9 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>
|
|||||||
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
||||||
|
|
||||||
if (httpChannel) {
|
if (httpChannel) {
|
||||||
|
// Spec step 5
|
||||||
|
SetAuthorRequestHeadersOnChannel(httpChannel);
|
||||||
|
|
||||||
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
|
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
|
||||||
|
|
||||||
if (!IsSystemXHR()) {
|
if (!IsSystemXHR()) {
|
||||||
@@ -2534,15 +2537,14 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>
|
|||||||
net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
|
net::InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1;
|
||||||
|
|
||||||
if (postDataStream) {
|
if (postDataStream) {
|
||||||
// If no content type header was set by the client, we set it to
|
// If author set no Content-Type, use the default from GetRequestBody().
|
||||||
// application/xml.
|
|
||||||
nsAutoCString contentType;
|
nsAutoCString contentType;
|
||||||
if (NS_FAILED(httpChannel->
|
|
||||||
GetRequestHeader(NS_LITERAL_CSTRING("Content-Type"),
|
GetAuthorRequestHeaderValue("content-type", contentType);
|
||||||
contentType))) {
|
if (contentType.IsVoid()) {
|
||||||
contentType = defaultContentType;
|
contentType = defaultContentType;
|
||||||
|
|
||||||
if (!charset.IsEmpty() && !contentType.IsVoid()) {
|
if (!charset.IsEmpty()) {
|
||||||
// If we are providing the default content type, then we also need to
|
// If we are providing the default content type, then we also need to
|
||||||
// provide a charset declaration.
|
// provide a charset declaration.
|
||||||
contentType.Append(NS_LITERAL_CSTRING(";charset="));
|
contentType.Append(NS_LITERAL_CSTRING(";charset="));
|
||||||
@@ -2720,8 +2722,26 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>
|
|||||||
|
|
||||||
// Set up the preflight if needed
|
// Set up the preflight if needed
|
||||||
if (!IsSystemXHR()) {
|
if (!IsSystemXHR()) {
|
||||||
|
nsTArray<nsCString> CORSUnsafeHeaders;
|
||||||
|
const char *kCrossOriginSafeHeaders[] = {
|
||||||
|
"accept", "accept-language", "content-language", "content-type",
|
||||||
|
"last-event-id"
|
||||||
|
};
|
||||||
|
for (RequestHeader& header : mAuthorRequestHeaders) {
|
||||||
|
bool safe = false;
|
||||||
|
for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
|
||||||
|
if (header.name.EqualsASCII(kCrossOriginSafeHeaders[i])) {
|
||||||
|
safe = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!safe) {
|
||||||
|
CORSUnsafeHeaders.AppendElement(header.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
|
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
|
||||||
loadInfo->SetCorsPreflightInfo(mCORSUnsafeHeaders,
|
loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
|
||||||
mFlagHadUploadListenersOnSend);
|
mFlagHadUploadListenersOnSend);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2847,99 +2867,62 @@ XMLHttpRequestMainThread::Send(nsIVariant* aVariant, const Nullable<RequestBody>
|
|||||||
|
|
||||||
// http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
|
// http://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-setrequestheader
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
XMLHttpRequestMainThread::SetRequestHeader(const nsACString& header,
|
XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
|
||||||
const nsACString& value)
|
const nsACString& aValue)
|
||||||
{
|
{
|
||||||
// Steps 1 and 2
|
// Steps 1 and 2
|
||||||
if (mState != State::opened || mFlagSend) {
|
if (mState != State::opened || mFlagSend) {
|
||||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||||
}
|
}
|
||||||
NS_ASSERTION(mChannel, "mChannel must be valid if we're OPENED.");
|
|
||||||
|
|
||||||
// Step 3
|
// Step 3
|
||||||
// Make sure we don't store an invalid header name in mCORSUnsafeHeaders
|
nsAutoCString value(aValue);
|
||||||
if (!NS_IsValidHTTPToken(header)) {
|
static const char kHTTPWhitespace[] = "\n\t\r ";
|
||||||
|
value.Trim(kHTTPWhitespace);
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
if (!NS_IsValidHTTPToken(aName) || !NS_IsReasonableHTTPHeaderValue(value)) {
|
||||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
return NS_ERROR_DOM_SYNTAX_ERR;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mChannel) // open() initializes mChannel, and open()
|
// Step 5
|
||||||
return NS_ERROR_FAILURE; // must be called before first setRequestHeader()
|
bool isPrivilegedCaller = IsSystemXHR();
|
||||||
|
bool isForbiddenHeader = nsContentUtils::IsForbiddenRequestHeader(aName);
|
||||||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
|
if (!isPrivilegedCaller && isForbiddenHeader) {
|
||||||
if (!httpChannel) {
|
NS_WARNING("refusing to set request header");
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will merge XHR headers, per the spec (secion 4.6.2) unless:
|
// Step 6.1
|
||||||
// 1 - The caller is privileged and setting an invalid header,
|
nsAutoCString lowercaseName;
|
||||||
// or
|
nsContentUtils::ASCIIToLower(aName, lowercaseName);
|
||||||
// 2 - we have not yet explicitly set that header; this allows web
|
|
||||||
// content to override default headers the first time they set them.
|
|
||||||
bool mergeHeaders = true;
|
|
||||||
|
|
||||||
if (!IsSystemXHR()) {
|
// Step 6.2
|
||||||
// Step 5: Check for dangerous headers.
|
bool notAlreadySet = true;
|
||||||
// Prevent modification to certain HTTP headers (see bug 302263), unless
|
for (RequestHeader& header : mAuthorRequestHeaders) {
|
||||||
// the executing script is privileged.
|
if (header.name.Equals(lowercaseName)) {
|
||||||
if (nsContentUtils::IsForbiddenRequestHeader(header)) {
|
// Gecko-specific: invalid headers can be set by privileged
|
||||||
NS_WARNING("refusing to set request header");
|
// callers, but will not merge.
|
||||||
return NS_OK;
|
if (isPrivilegedCaller && isForbiddenHeader) {
|
||||||
}
|
header.value.Assign(value);
|
||||||
|
} else {
|
||||||
// Check for dangerous cross-site headers
|
header.value.AppendLiteral(", ");
|
||||||
bool safeHeader = IsSystemXHR();
|
header.value.Append(value);
|
||||||
if (!safeHeader) {
|
|
||||||
// Content-Type isn't always safe, but we'll deal with it in Send()
|
|
||||||
const char *kCrossOriginSafeHeaders[] = {
|
|
||||||
"accept", "accept-language", "content-language", "content-type",
|
|
||||||
"last-event-id"
|
|
||||||
};
|
|
||||||
for (uint32_t i = 0; i < ArrayLength(kCrossOriginSafeHeaders); ++i) {
|
|
||||||
if (header.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
|
|
||||||
safeHeader = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
notAlreadySet = false;
|
||||||
|
break;
|
||||||
if (!safeHeader) {
|
|
||||||
if (!mCORSUnsafeHeaders.Contains(header, nsCaseInsensitiveCStringArrayComparator())) {
|
|
||||||
mCORSUnsafeHeaders.AppendElement(header);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Case 1 above
|
|
||||||
if (nsContentUtils::IsForbiddenSystemRequestHeader(header)) {
|
|
||||||
mergeHeaders = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mAlreadySetHeaders.Contains(header)) {
|
// Step 6.3
|
||||||
// Case 2 above
|
if (notAlreadySet) {
|
||||||
mergeHeaders = false;
|
RequestHeader newHeader = {
|
||||||
}
|
nsCString(lowercaseName), nsCString(value)
|
||||||
|
|
||||||
nsresult rv;
|
|
||||||
if (value.IsEmpty()) {
|
|
||||||
rv = httpChannel->SetEmptyRequestHeader(header);
|
|
||||||
} else {
|
|
||||||
// Merge headers depending on what we decided above.
|
|
||||||
rv = httpChannel->SetRequestHeader(header, value, mergeHeaders);
|
|
||||||
}
|
|
||||||
if (rv == NS_ERROR_INVALID_ARG) {
|
|
||||||
return NS_ERROR_DOM_SYNTAX_ERR;
|
|
||||||
}
|
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
// Remember that we've set this header, so subsequent set operations will merge values.
|
|
||||||
mAlreadySetHeaders.PutEntry(nsCString(header));
|
|
||||||
|
|
||||||
// We'll want to duplicate this header for any replacement channels (eg. on redirect)
|
|
||||||
RequestHeader reqHeader = {
|
|
||||||
nsCString(header), nsCString(value)
|
|
||||||
};
|
};
|
||||||
mModifiedRequestHeaders.AppendElement(reqHeader);
|
mAuthorRequestHeaders.AppendElement(newHeader);
|
||||||
}
|
}
|
||||||
return rv;
|
|
||||||
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
@@ -3179,6 +3162,32 @@ XMLHttpRequestMainThread::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
XMLHttpRequestMainThread::GetAuthorRequestHeaderValue(const char* aName,
|
||||||
|
nsACString& outValue)
|
||||||
|
{
|
||||||
|
for (RequestHeader& header : mAuthorRequestHeaders) {
|
||||||
|
if (header.name.Equals(aName)) {
|
||||||
|
outValue.Assign(header.value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outValue.SetIsVoid(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
XMLHttpRequestMainThread::SetAuthorRequestHeadersOnChannel(
|
||||||
|
nsCOMPtr<nsIHttpChannel> aHttpChannel)
|
||||||
|
{
|
||||||
|
for (RequestHeader& header : mAuthorRequestHeaders) {
|
||||||
|
if (header.value.IsEmpty()) {
|
||||||
|
aHttpChannel->SetEmptyRequestHeader(header.name);
|
||||||
|
} else {
|
||||||
|
aHttpChannel->SetRequestHeader(header.name, header.value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nsresult
|
nsresult
|
||||||
XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
|
XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
|
||||||
{
|
{
|
||||||
@@ -3191,15 +3200,7 @@ XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
|
|||||||
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
|
||||||
if (httpChannel) {
|
if (httpChannel) {
|
||||||
// Ensure all original headers are duplicated for the new channel (bug #553888)
|
// Ensure all original headers are duplicated for the new channel (bug #553888)
|
||||||
for (RequestHeader& requestHeader : mModifiedRequestHeaders) {
|
SetAuthorRequestHeadersOnChannel(httpChannel);
|
||||||
if (requestHeader.value.IsEmpty()) {
|
|
||||||
httpChannel->SetEmptyRequestHeader(requestHeader.header);
|
|
||||||
} else {
|
|
||||||
httpChannel->SetRequestHeader(requestHeader.header,
|
|
||||||
requestHeader.value,
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
mErrorLoad = true;
|
mErrorLoad = true;
|
||||||
@@ -3515,9 +3516,8 @@ XMLHttpRequestMainThread::ShouldBlockAuthPrompt()
|
|||||||
// Verify that it's ok to prompt for credentials here, per spec
|
// Verify that it's ok to prompt for credentials here, per spec
|
||||||
// http://xhr.spec.whatwg.org/#the-send%28%29-method
|
// http://xhr.spec.whatwg.org/#the-send%28%29-method
|
||||||
|
|
||||||
for (uint32_t i = 0, len = mModifiedRequestHeaders.Length(); i < len; ++i) {
|
for (RequestHeader& requestHeader : mAuthorRequestHeaders) {
|
||||||
if (mModifiedRequestHeaders[i].header.
|
if (requestHeader.name.EqualsLiteral("authorization")) {
|
||||||
LowerCaseEqualsLiteral("authorization")) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,10 +220,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
virtual void
|
virtual void
|
||||||
SetRequestHeader(const nsACString& aHeader, const nsACString& aValue,
|
SetRequestHeader(const nsACString& aName, const nsACString& aValue,
|
||||||
ErrorResult& aRv) override
|
ErrorResult& aRv) override
|
||||||
{
|
{
|
||||||
aRv = SetRequestHeader(aHeader, aValue);
|
aRv = SetRequestHeader(aName, aValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual uint32_t
|
virtual uint32_t
|
||||||
@@ -620,7 +620,6 @@ protected:
|
|||||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||||
nsCOMPtr<nsIChannel> mChannel;
|
nsCOMPtr<nsIChannel> mChannel;
|
||||||
nsCOMPtr<nsIDocument> mResponseXML;
|
nsCOMPtr<nsIDocument> mResponseXML;
|
||||||
nsTArray<nsCString> mCORSUnsafeHeaders;
|
|
||||||
|
|
||||||
nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
|
nsCOMPtr<nsIStreamListener> mXMLParserStreamListener;
|
||||||
|
|
||||||
@@ -791,12 +790,13 @@ protected:
|
|||||||
|
|
||||||
struct RequestHeader
|
struct RequestHeader
|
||||||
{
|
{
|
||||||
nsCString header;
|
nsCString name;
|
||||||
nsCString value;
|
nsCString value;
|
||||||
};
|
};
|
||||||
nsTArray<RequestHeader> mModifiedRequestHeaders;
|
nsTArray<RequestHeader> mAuthorRequestHeaders;
|
||||||
|
|
||||||
nsTHashtable<nsCStringHashKey> mAlreadySetHeaders;
|
void GetAuthorRequestHeaderValue(const char* aName, nsACString& outValue);
|
||||||
|
void SetAuthorRequestHeadersOnChannel(nsCOMPtr<nsIHttpChannel> aChannel);
|
||||||
|
|
||||||
// Helper object to manage our XPCOM scriptability bits
|
// Helper object to manage our XPCOM scriptability bits
|
||||||
nsXMLHttpRequestXPCOMifier* mXPCOMifier;
|
nsXMLHttpRequestXPCOMifier* mXPCOMifier;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ var headers = [
|
|||||||
"coNtEnt-LEngth",
|
"coNtEnt-LEngth",
|
||||||
"CoOKIe",
|
"CoOKIe",
|
||||||
"cOOkiE2",
|
"cOOkiE2",
|
||||||
"cOntEnt-tRAnsFer-enCoDiNg",
|
|
||||||
"DATE",
|
"DATE",
|
||||||
"dNT",
|
"dNT",
|
||||||
"exPeCt",
|
"exPeCt",
|
||||||
@@ -54,6 +53,7 @@ function startTest() {
|
|||||||
request.open("GET", window.location.href);
|
request.open("GET", window.location.href);
|
||||||
for (i = 0; i < headers.length; i++)
|
for (i = 0; i < headers.length; i++)
|
||||||
request.setRequestHeader(headers[i], "test" + i);
|
request.setRequestHeader(headers[i], "test" + i);
|
||||||
|
request.send(); // headers aren't set on the channel until send()
|
||||||
|
|
||||||
// Read out headers
|
// Read out headers
|
||||||
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||||
@@ -73,6 +73,7 @@ function startTest() {
|
|||||||
request.open("GET", window.location.href);
|
request.open("GET", window.location.href);
|
||||||
for (i = 0; i < headers.length; i++)
|
for (i = 0; i < headers.length; i++)
|
||||||
request.setRequestHeader(headers[i], "test" + i);
|
request.setRequestHeader(headers[i], "test" + i);
|
||||||
|
request.send(); // headers aren't set on the channel until send()
|
||||||
|
|
||||||
// Read out headers
|
// Read out headers
|
||||||
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
var channel = SpecialPowers.wrap(request).channel.QueryInterface(SpecialPowers.Ci.nsIHttpChannel);
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
[setrequestheader-case-insensitive.htm]
|
|
||||||
type: testharness
|
|
||||||
[XMLHttpRequest: setRequestHeader() - headers that differ in case]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[setrequestheader-header-allowed.htm]
|
|
||||||
type: testharness
|
|
||||||
[XMLHttpRequest: setRequestHeader() - headers that are allowed (Authorization)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[XMLHttpRequest: setRequestHeader() - headers that are allowed (Content-Transfer-Encoding)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[XMLHttpRequest: setRequestHeader() - headers that are allowed (Content-Type)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[XMLHttpRequest: setRequestHeader() - headers that are allowed (User-Agent)]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user