Bug 1784880 - Support range requests on blob URLs in fetch/XMLHttpRequest; r=dlrobertson,necko-reviewers,jesup

Differential Revision: https://phabricator.services.mozilla.com/D184281
This commit is contained in:
Thomas Wisniewski
2023-08-01 18:50:37 +00:00
parent a6ab329536
commit 1ffb72e885
14 changed files with 523 additions and 180 deletions

View File

@@ -811,6 +811,28 @@ bool XMLHttpRequestMainThread::IsDeniedCrossSiteCORSRequest() {
return false;
}
Maybe<nsBaseChannel::ContentRange>
XMLHttpRequestMainThread::GetRequestedContentRange() const {
MOZ_ASSERT(mChannel);
if (!IsBlobURI(mRequestURL)) {
return mozilla::Nothing();
}
nsBaseChannel* baseChan = static_cast<nsBaseChannel*>(mChannel.get());
if (!baseChan) {
return mozilla::Nothing();
}
return baseChan->GetContentRange();
}
void XMLHttpRequestMainThread::GetContentRangeHeader(nsACString& out) const {
Maybe<nsBaseChannel::ContentRange> range = GetRequestedContentRange();
if (range.isSome()) {
range->AsHeader(out);
} else {
out.SetIsVoid(true);
}
}
void XMLHttpRequestMainThread::GetResponseURL(nsAString& aUrl) {
aUrl.Truncate();
@@ -867,8 +889,8 @@ uint32_t XMLHttpRequestMainThread::GetStatus(ErrorResult& aRv) {
nsCOMPtr<nsIHttpChannel> httpChannel = GetCurrentHttpChannel();
if (!httpChannel) {
// Pretend like we got a 200 response, since our load was successful
return 200;
// Pretend like we got a 200/206 response, since our load was successful
return GetRequestedContentRange().isSome() ? 206 : 200;
}
uint32_t status;
@@ -1175,6 +1197,17 @@ void XMLHttpRequestMainThread::GetAllResponseHeaders(
aResponseHeaders.AppendLiteral("\r\n");
}
}
// Should set a Content-Range header for blob scheme.
// From https://fetch.spec.whatwg.org/#scheme-fetch 3.blob.9.20:
// "Set responses header list to «(`Content-Length`, serializedSlicedLength),
// (`Content-Type`, type), (`Content-Range`, contentRange)»."
GetContentRangeHeader(value);
if (!value.IsVoid()) {
aResponseHeaders.AppendLiteral("Content-Range: ");
aResponseHeaders.Append(value);
aResponseHeaders.AppendLiteral("\r\n");
}
}
void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
@@ -1229,6 +1262,11 @@ void XMLHttpRequestMainThread::GetResponseHeader(const nsACString& header,
}
}
// Content Range:
else if (header.LowerCaseEqualsASCII("content-range")) {
GetContentRangeHeader(_retval);
}
return;
}
@@ -1865,6 +1903,13 @@ XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
return NS_OK;
}
// If we were asked for a bad range on a blob URL, but we're async,
// we should throw now in order to fire an error progress event.
if (GetRequestedContentRange().isNothing() &&
mAuthorRequestHeaders.Has("range")) {
return NS_ERROR_NET_PARTIAL_TRANSFER;
}
nsCOMPtr<nsIChannel> channel(do_QueryInterface(request));
NS_ENSURE_TRUE(channel, NS_ERROR_UNEXPECTED);
@@ -1905,11 +1950,13 @@ XMLHttpRequestMainThread::OnStartRequest(nsIRequest* request) {
channel->SetContentType(NS_ConvertUTF16toUTF8(mOverrideMimeType));
}
// Fallback to 'application/octet-stream'
nsAutoCString type;
channel->GetContentType(type);
if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
// Fallback to 'application/octet-stream' (leaving data URLs alone)
if (!IsBlobURI(mRequestURL)) {
nsAutoCString type;
channel->GetContentType(type);
if (type.IsEmpty() || type.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
channel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM));
}
}
DetectCharset();
@@ -2665,6 +2712,21 @@ nsresult XMLHttpRequestMainThread::InitiateFetch(
}
}
// Should set a Content-Range header for blob scheme, and also slice the
// blob appropriately, so we process the Range header here for later use.
if (IsBlobURI(mRequestURL)) {
nsAutoCString range;
mAuthorRequestHeaders.Get("range", range);
if (!range.IsVoid()) {
rv = NS_SetChannelContentRangeForBlobURI(mChannel, mRequestURL, range);
if (mFlagSynchronous && NS_FAILED(rv)) {
// We later fire an error progress event for non-sync
mState = XMLHttpRequest_Binding::DONE;
return NS_ERROR_DOM_NETWORK_ERR;
}
}
}
// Due to the chrome-only XHR.channel API, we need a hacky way to set the
// SEC_COOKIES_INCLUDE *after* the channel has been has been created, since
// .withCredentials can be called after open() is called.
@@ -2813,6 +2875,11 @@ already_AddRefed<PreloaderBase> XMLHttpRequestMainThread::FindPreload() {
void XMLHttpRequestMainThread::EnsureChannelContentType() {
MOZ_ASSERT(mChannel);
// We don't mess with the content type of a blob URL.
if (IsBlobURI(mRequestURL)) {
return;
}
// Since we expect XML data, set the type hint accordingly
// if the channel doesn't know any content type.
// This means that we always try to parse local files as XML