Bug 1285036 - Part 10: Factor the request-header XHR code out into its own class. r=smaug

This commit is contained in:
Thomas Wisniewski
2016-08-18 20:15:37 -04:00
parent cd1771782e
commit 67fbd6985b
2 changed files with 249 additions and 136 deletions

View File

@@ -2531,8 +2531,13 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
// If the user hasn't overridden the Accept header, set it to */* as per spec
if (!mAuthorRequestHeaders.Has("accept")) {
mAuthorRequestHeaders.Set("Accept", NS_LITERAL_CSTRING("*/*"));
}
// Spec step 5
SetAuthorRequestHeadersOnChannel(httpChannel);
mAuthorRequestHeaders.ApplyToChannel(httpChannel);
httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
@@ -2543,15 +2548,6 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
httpChannel, mozilla::net::RP_Default);
}
// If the user hasn't overridden the Accept header, set it to */* as per spec
nsAutoCString acceptHeader;
GetAuthorRequestHeaderValue("accept", acceptHeader);
if (acceptHeader.IsVoid()) {
httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
NS_LITERAL_CSTRING("*/*"),
false);
}
// Some extensions override the http protocol handler and provide their own
// implementation. The channels returned from that implementation doesn't
// seem to always implement the nsIUploadChannel2 interface, presumably
@@ -2599,7 +2595,7 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
// If author set no Content-Type, use the default from GetAsStream().
nsAutoCString contentType;
GetAuthorRequestHeaderValue("content-type", contentType);
mAuthorRequestHeaders.Get("content-type", contentType);
if (contentType.IsVoid()) {
contentType = defaultContentType;
@@ -2613,52 +2609,14 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
// We don't want to set a charset for streams.
if (!charset.IsEmpty()) {
nsAutoCString specifiedCharset;
bool haveCharset;
int32_t charsetStart, charsetEnd;
rv = NS_ExtractCharsetFromContentType(contentType, specifiedCharset,
&haveCharset, &charsetStart,
&charsetEnd);
while (NS_SUCCEEDED(rv) && haveCharset) {
// special case: the extracted charset is quoted with single quotes
// -- for the purpose of preserving what was set we want to handle
// them as delimiters (although they aren't really)
if (specifiedCharset.Length() >= 2 &&
specifiedCharset.First() == '\'' &&
specifiedCharset.Last() == '\'') {
specifiedCharset = Substring(specifiedCharset, 1,
specifiedCharset.Length() - 2);
// Replace all case-insensitive matches of the charset in the
// content-type with the correct case.
RequestHeaders::CharsetIterator iter(contentType);
const nsCaseInsensitiveCStringComparator cmp;
while (iter.Next()) {
if (!iter.Equals(charset, cmp)) {
iter.Replace(charset);
}
// If the content-type the page set already has a charset parameter,
// and it's the same charset, up to case, as |charset|, just send the
// page-set content-type header. Apparently at least
// google-web-toolkit is broken and relies on the exact case of its
// charset parameter, which makes things break if we use |charset|
// (which is always a fully resolved charset per our charset alias
// table, hence might be differently cased).
if (!specifiedCharset.Equals(charset,
nsCaseInsensitiveCStringComparator())) {
// Find the start of the actual charset declaration, skipping the
// "; charset=" to avoid modifying whitespace.
int32_t charIdx =
Substring(contentType, charsetStart,
charsetEnd - charsetStart).FindChar('=') + 1;
MOZ_ASSERT(charIdx != -1);
contentType.Replace(charsetStart + charIdx,
charsetEnd - charsetStart - charIdx,
charset);
}
// Look for another charset declaration in the string, limiting the
// search to only look for charsets before the current charset, to
// prevent finding the same charset twice.
nsDependentCSubstring interestingSection =
Substring(contentType, 0, charsetStart);
rv = NS_ExtractCharsetFromContentType(interestingSection,
specifiedCharset, &haveCharset,
&charsetStart, &charsetEnd);
}
}
@@ -2782,23 +2740,7 @@ XMLHttpRequestMainThread::SendInternal(const RequestBodyBase* aBody)
// Set up the preflight if needed
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.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
safe = true;
break;
}
}
if (!safe) {
CORSUnsafeHeaders.AppendElement(header.name);
}
}
mAuthorRequestHeaders.GetCORSUnsafeHeaders(CORSUnsafeHeaders);
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
loadInfo->SetCorsPreflightInfo(CORSUnsafeHeaders,
mFlagHadUploadListenersOnSend);
@@ -2968,30 +2910,13 @@ XMLHttpRequestMainThread::SetRequestHeader(const nsACString& aName,
// Skipping for now, as normalizing the case of header names may not be
// web-compatible. See bug 1285036.
// Step 6.2
bool notAlreadySet = true;
const nsCaseInsensitiveCStringComparator ignoreCase;
for (RequestHeader& header : mAuthorRequestHeaders) {
if (header.name.Equals(aName, ignoreCase)) {
// Gecko-specific: invalid headers can be set by privileged
// callers, but will not merge.
if (isPrivilegedCaller && isForbiddenHeader) {
header.value.Assign(value);
} else {
header.value.AppendLiteral(", ");
header.value.Append(value);
}
notAlreadySet = false;
break;
}
}
// Step 6.3
if (notAlreadySet) {
RequestHeader newHeader = {
nsCString(aName), nsCString(value)
};
mAuthorRequestHeaders.AppendElement(newHeader);
// Step 6.2-6.3
// Gecko-specific: invalid headers can be set by privileged
// callers, but will not merge.
if (isPrivilegedCaller && isForbiddenHeader) {
mAuthorRequestHeaders.Set(aName, value);
} else {
mAuthorRequestHeaders.MergeOrSet(aName, value);
}
return NS_OK;
@@ -3232,32 +3157,6 @@ XMLHttpRequestMainThread::AsyncOnChannelRedirect(nsIChannel *aOldChannel,
return NS_OK;
}
void
XMLHttpRequestMainThread::GetAuthorRequestHeaderValue(const char* aName,
nsACString& outValue)
{
for (RequestHeader& header : mAuthorRequestHeaders) {
if (header.name.EqualsIgnoreCase(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
XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
{
@@ -3270,7 +3169,7 @@ XMLHttpRequestMainThread::OnRedirectVerifyCallback(nsresult result)
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
if (httpChannel) {
// Ensure all original headers are duplicated for the new channel (bug #553888)
SetAuthorRequestHeadersOnChannel(httpChannel);
mAuthorRequestHeaders.ApplyToChannel(httpChannel);
}
} else {
mErrorLoad = true;
@@ -3563,9 +3462,7 @@ XMLHttpRequestMainThread::ShouldBlockAuthPrompt()
// Verify that it's ok to prompt for credentials here, per spec
// http://xhr.spec.whatwg.org/#the-send%28%29-method
nsAutoCString contentType;
GetAuthorRequestHeaderValue("authorization", contentType);
if (!contentType.IsVoid()) {
if (mAuthorRequestHeaders.Has("authorization")) {
return true;
}
@@ -3837,5 +3734,192 @@ ArrayBufferBuilder::areOverlappingRegions(const uint8_t* aStart1,
return max_start < min_end;
}
RequestHeaders::RequestHeader*
RequestHeaders::Find(const nsACString& aName)
{
const nsCaseInsensitiveCStringComparator ignoreCase;
for (RequestHeaders::RequestHeader& header : mHeaders) {
if (header.mName.Equals(aName, ignoreCase)) {
return &header;
}
}
return nullptr;
}
bool
RequestHeaders::Has(const char* aName)
{
return Has(nsDependentCString(aName));
}
bool
RequestHeaders::Has(const nsACString& aName)
{
return !!Find(aName);
}
void
RequestHeaders::Get(const char* aName, nsACString& aValue)
{
Get(nsDependentCString(aName), aValue);
}
void
RequestHeaders::Get(const nsACString& aName, nsACString& aValue)
{
RequestHeader* header = Find(aName);
if (header) {
aValue = header->mValue;
} else {
aValue.SetIsVoid(true);
}
}
void
RequestHeaders::Set(const char* aName, const nsACString& aValue)
{
Set(nsDependentCString(aName), aValue);
}
void
RequestHeaders::Set(const nsACString& aName, const nsACString& aValue)
{
RequestHeader* header = Find(aName);
if (header) {
header->mValue.Assign(aValue);
} else {
RequestHeader newHeader = {
nsCString(aName), nsCString(aValue)
};
mHeaders.AppendElement(newHeader);
}
}
void
RequestHeaders::MergeOrSet(const char* aName, const nsACString& aValue)
{
MergeOrSet(nsDependentCString(aName), aValue);
}
void
RequestHeaders::MergeOrSet(const nsACString& aName, const nsACString& aValue)
{
RequestHeader* header = Find(aName);
if (header) {
header->mValue.AppendLiteral(", ");
header->mValue.Append(aValue);
} else {
RequestHeader newHeader = {
nsCString(aName), nsCString(aValue)
};
mHeaders.AppendElement(newHeader);
}
}
void
RequestHeaders::Clear()
{
mHeaders.Clear();
}
void
RequestHeaders::ApplyToChannel(nsIHttpChannel* aHttpChannel) const
{
for (const RequestHeader& header : mHeaders) {
if (header.mValue.IsEmpty()) {
aHttpChannel->SetEmptyRequestHeader(header.mName);
} else {
aHttpChannel->SetRequestHeader(header.mName, header.mValue, false);
}
}
}
void
RequestHeaders::GetCORSUnsafeHeaders(nsTArray<nsCString>& aArray) const
{
static const char *kCrossOriginSafeHeaders[] = {
"accept", "accept-language", "content-language", "content-type",
"last-event-id"
};
const uint32_t kCrossOriginSafeHeadersLength =
ArrayLength(kCrossOriginSafeHeaders);
for (const RequestHeader& header : mHeaders) {
bool safe = false;
for (uint32_t i = 0; i < kCrossOriginSafeHeadersLength; ++i) {
if (header.mName.LowerCaseEqualsASCII(kCrossOriginSafeHeaders[i])) {
safe = true;
break;
}
}
if (!safe) {
aArray.AppendElement(header.mName);
}
}
}
RequestHeaders::CharsetIterator::CharsetIterator(nsACString& aSource) :
mValid(false),
mCurPos(-1),
mCurLen(-1),
mCutoff(aSource.Length()),
mSource(aSource)
{
}
bool
RequestHeaders::CharsetIterator::Equals(const nsACString& aOther,
const nsCStringComparator& aCmp) const
{
if (mValid) {
return Substring(mSource, mCurPos, mCurLen).Equals(aOther, aCmp);
} else {
return false;
}
}
void
RequestHeaders::CharsetIterator::Replace(const nsACString& aReplacement)
{
if (mValid) {
mSource.Replace(mCurPos, mCurLen, aReplacement);
mCurLen = aReplacement.Length();
}
}
bool
RequestHeaders::CharsetIterator::Next()
{
int32_t start, end;
nsAutoCString charset;
// Look for another charset declaration in the string, limiting the
// search to only the characters before the parts we've already searched
// (before mCutoff), so that we don't find the same charset twice.
NS_ExtractCharsetFromContentType(Substring(mSource, 0, mCutoff),
charset, &mValid, &start, &end);
if (!mValid) {
return false;
}
// Everything after the = sign is the part of the charset we want.
mCurPos = mSource.FindChar('=', start) + 1;
mCurLen = end - mCurPos;
// Special case: the extracted charset is quoted with single quotes.
// For the purpose of preserving what was set we want to handle them
// as delimiters (although they aren't really).
if (charset.Length() >= 2 &&
charset.First() == '\'' &&
charset.Last() == '\'') {
++mCurPos;
mCurLen -= 2;
}
mCutoff = start;
return true;
}
} // dom namespace
} // mozilla namespaceo