Bug 1285036 - Part 7: Change SetRequestHeader() and related header code to follow the spec more closely. r=baku

This commit is contained in:
Thomas Wisniewski
2016-07-20 12:22:43 -04:00
parent a74b606e1d
commit be02be7b7a
6 changed files with 103 additions and 122 deletions

View File

@@ -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])) {

View File

@@ -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;
} }
} }

View File

@@ -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;

View File

@@ -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);

View File

@@ -1,5 +0,0 @@
[setrequestheader-case-insensitive.htm]
type: testharness
[XMLHttpRequest: setRequestHeader() - headers that differ in case]
expected: FAIL

View File

@@ -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