Bug 1754004 - Part 7: Consistently normalize upload streams passed to HTTP channels, r=asuth,necko-reviewers,dragana

Unfortunately, upload streams used by necko have various odd behaviours
and requirements which happened to be usually preserved by the previous
IPC serialization logic, but were not consistently preserved. This
includes requiring the stream to be synchronous (as some consumers such
as WebExtensions and DevTools appear to read it assuming Available() is
the stream length), seekable (as it needs to be rewound in various
places), and cloneable (as the stream information is often handed out to
other components).

In addition, the WebExtension WebRequest code makes assumptions about
the specific topology of the input stream for optimization purposes,
meaning that nsMultiplexInputStreams need to be preserved.

The way this was previously handled was by copying the entire payload
into a nsStorageStream as an async operation. This happened very
infrequently in out test suite, however, and had some issues. It could
lead to data loss if the stream was a nsMIMEInputStream (as the metadata
would be lost), and would destroy the topology required by WebRequest.

This patch changes the code to instead manually walk and replace streams
in the input stream's data structure, to efficiently copy only the
required data, preserve the invariants, and make the type seekable
before AsyncOpen continues. This helps keep the complexity of the
invariants HTTPChannel depends on out of generic input stream handling
code.

In addition, due to how early this happens, it replaces the need for
PartiallySeekableInputStream which will be removed a later part.

Differential Revision: https://phabricator.services.mozilla.com/D141044
This commit is contained in:
Nika Layzell
2022-05-02 20:44:24 +00:00
parent 5c9a8765b4
commit 920f727ddb
10 changed files with 333 additions and 211 deletions

View File

@@ -2064,6 +2064,7 @@ void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel* aChannel,
ErrorResult& aRv) { ErrorResult& aRv) {
MOZ_ASSERT(aChannel); MOZ_ASSERT(aChannel);
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIChannel> internalChannel; nsCOMPtr<nsIChannel> internalChannel;
aRv = aChannel->GetChannel(getter_AddRefs(internalChannel)); aRv = aChannel->GetChannel(getter_AddRefs(internalChannel));
@@ -2232,28 +2233,13 @@ void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel* aChannel,
// When this service worker was registered, we also sent down the permissions // When this service worker was registered, we also sent down the permissions
// for the runnable. They should have arrived by now, but we still need to // for the runnable. They should have arrived by now, but we still need to
// wait for them if they have not. // wait for them if they have not.
nsCOMPtr<nsIRunnable> permissionsRunnable = NS_NewRunnableFunction( RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance();
"dom::ServiceWorkerManager::DispatchFetchEvent", [=]() { if (permMgr) {
RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance(); permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
if (permMgr) { continueRunnable);
permMgr->WhenPermissionsAvailable(serviceWorker->Principal(), } else {
continueRunnable); continueRunnable->HandleError();
} else {
continueRunnable->HandleError();
}
});
nsCOMPtr<nsIUploadChannel2> uploadChannel =
do_QueryInterface(internalChannel);
// If there is no upload stream, then continue immediately
if (!uploadChannel) {
MOZ_ALWAYS_SUCCEEDS(permissionsRunnable->Run());
return;
} }
// Otherwise, ensure the upload stream can be cloned directly. This may
// require some async copying, so provide a callback.
aRv = uploadChannel->EnsureUploadStreamIsCloneable(permissionsRunnable);
} }
bool ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI, bool ServiceWorkerManager::IsAvailable(nsIPrincipal* aPrincipal, nsIURI* aURI,

View File

@@ -48,19 +48,7 @@ interface nsIUploadChannel2 : nsISupports
readonly attribute boolean uploadStreamHasHeaders; readonly attribute boolean uploadStreamHasHeaders;
/** /**
* Ensure the upload stream, if any, is cloneable. This may involve * Clones the upload stream. May only be called in the parent process.
* async copying, so a callback runnable must be provided. It will
* invoked on the current thread when the upload stream is ready
* for cloning. If the stream is already cloneable, then the callback
* will be invoked synchronously.
*/
[noscript]
void ensureUploadStreamIsCloneable(in nsIRunnable aCallback);
/**
* Clones the upload stream. May return failure if the upload stream
* is not cloneable. If this is not acceptable, use the
* ensureUploadStreamIsCloneable() method first.
* aContentLength could be -1 in case the size of the stream is unknown, * aContentLength could be -1 in case the size of the stream is unknown,
* otherwise it will contain the known size of the stream. * otherwise it will contain the known size of the stream.
*/ */

View File

@@ -15,6 +15,7 @@
#include "HttpLog.h" #include "HttpLog.h"
#include "LoadInfo.h" #include "LoadInfo.h"
#include "ReferrerInfo.h" #include "ReferrerInfo.h"
#include "mozIRemoteLazyInputStream.h"
#include "mozIThirdPartyUtil.h" #include "mozIThirdPartyUtil.h"
#include "mozilla/AntiTrackingUtils.h" #include "mozilla/AntiTrackingUtils.h"
#include "mozilla/BasePrincipal.h" #include "mozilla/BasePrincipal.h"
@@ -41,9 +42,10 @@
#include "mozilla/dom/ProcessIsolation.h" #include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/net/OpaqueResponseUtils.h" #include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/net/PartiallySeekableInputStream.h"
#include "mozilla/net/UrlClassifierCommon.h" #include "mozilla/net/UrlClassifierCommon.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h" #include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsBufferedStreams.h"
#include "nsCOMPtr.h"
#include "nsCRT.h" #include "nsCRT.h"
#include "nsContentSecurityManager.h" #include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h" #include "nsContentSecurityUtils.h"
@@ -66,6 +68,7 @@
#include "nsIHttpHeaderVisitor.h" #include "nsIHttpHeaderVisitor.h"
#include "nsILoadGroupChild.h" #include "nsILoadGroupChild.h"
#include "nsIMIMEInputStream.h" #include "nsIMIMEInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "nsIMutableArray.h" #include "nsIMutableArray.h"
#include "nsINetworkInterceptController.h" #include "nsINetworkInterceptController.h"
#include "nsIObserverService.h" #include "nsIObserverService.h"
@@ -866,13 +869,32 @@ HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
// So we need special case for GET method. // So we need special case for GET method.
StoreUploadStreamHasHeaders(false); StoreUploadStreamHasHeaders(false);
mRequestHead.SetMethod("GET"_ns); // revert to GET request mRequestHead.SetMethod("GET"_ns); // revert to GET request
mUploadStream = stream; mUploadStream = nullptr;
return NS_OK; return NS_OK;
} }
namespace { namespace {
void CopyComplete(void* aClosure, nsresult aStatus) { class MIMEHeaderCopyVisitor final : public nsIHttpHeaderVisitor {
public:
explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
NS_DECL_ISUPPORTS
NS_IMETHOD VisitHeader(const nsACString& aName,
const nsACString& aValue) override {
return mDest->AddHeader(PromiseFlatCString(aName).get(),
PromiseFlatCString(aValue).get());
}
private:
~MIMEHeaderCopyVisitor() = default;
nsCOMPtr<nsIMIMEInputStream> mDest;
};
NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
static void NormalizeCopyComplete(void* aClosure, nsresult aStatus) {
#ifdef DEBUG #ifdef DEBUG
// Called on the STS thread by NS_AsyncCopy // Called on the STS thread by NS_AsyncCopy
nsCOMPtr<nsIEventTarget> sts = nsCOMPtr<nsIEventTarget> sts =
@@ -882,108 +904,230 @@ void CopyComplete(void* aClosure, nsresult aStatus) {
MOZ_ASSERT(result, "Should only be called on the STS thread."); MOZ_ASSERT(result, "Should only be called on the STS thread.");
#endif #endif
auto* channel = static_cast<HttpBaseChannel*>(aClosure); RefPtr<GenericPromise::Private> ready =
channel->OnCopyComplete(aStatus); already_AddRefed(static_cast<GenericPromise::Private*>(aClosure));
if (NS_SUCCEEDED(aStatus)) {
ready->Resolve(true, __func__);
} else {
ready->Reject(aStatus, __func__);
}
} }
} // anonymous namespace // Normalize the upload stream for a HTTP channel, so that is one of the
// expected and compatible types. Components like WebExtensions and DevTools
// expect that upload streams in the parent process are cloneable, seekable, and
// synchronous to read, which this function helps guarantee somewhat efficiently
// and without loss of information.
//
// If the replacement stream outparameter is not initialized to `nullptr`, the
// returned stream should be used instead of `aUploadStream` as the upload
// stream for the HTTP channel, and the previous stream should not be touched
// again.
//
// If aReadyPromise is non-nullptr after the function is called, it is a promise
// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
// as the replacement stream will not be ready until it is resolved.
static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
nsIInputStream** aReplacementStream,
GenericPromise** aReadyPromise) {
MOZ_ASSERT(XRE_IsParentProcess());
NS_IMETHODIMP *aReplacementStream = nullptr;
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback) { *aReadyPromise = nullptr;
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
NS_ENSURE_ARG_POINTER(aCallback);
// We could in theory allow multiple callers to use this method, // Unwrap RemoteLazyInputStream and normalize the contents as we're in the
// but the complexity does not seem worth it yet. Just fail if // parent process.
// this is called more than once simultaneously. if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED); do_QueryInterface(aUploadStream)) {
nsCOMPtr<nsIInputStream> internal;
if (NS_SUCCEEDED(
lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
nsCOMPtr<nsIInputStream> replacement;
nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
// We can immediately exec the callback if we don't have an upload stream. if (replacement) {
if (!mUploadStream) { replacement.forget(aReplacementStream);
aCallback->Run(); } else {
internal.forget(aReplacementStream);
}
return NS_OK;
}
}
// Preserve MIME information on the stream when normalizing.
if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
nsCOMPtr<nsIInputStream> data;
nsresult rv = mime->GetData(getter_AddRefs(data));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> replacement;
rv =
NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
if (replacement) {
nsCOMPtr<nsIMIMEInputStream> replacementMime(
do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
new MIMEHeaderCopyVisitor(replacementMime);
rv = mime->VisitHeaders(visitor);
NS_ENSURE_SUCCESS(rv, rv);
rv = replacementMime->SetData(replacement);
NS_ENSURE_SUCCESS(rv, rv);
replacementMime.forget(aReplacementStream);
}
return NS_OK; return NS_OK;
} }
// Upload nsIInputStream must be cloneable and seekable in order to be // Preserve "real" buffered input streams which wrap data (i.e. are backed by
// processed by devtools network inspector. // nsBufferedInputStream), but normalize the wrapped stream.
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream); if (nsCOMPtr<nsIBufferedInputStream> buffered =
if (seekable && NS_InputStreamIsCloneable(mUploadStream)) { do_QueryInterface(aUploadStream)) {
aCallback->Run(); nsCOMPtr<nsIInputStream> data;
if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
nsCOMPtr<nsIInputStream> replacement;
nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
if (replacement) {
// This buffer size should be kept in sync with HTMLFormSubmission.
rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
8192);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
}
// Preserve multiplex input streams, normalizing each individual inner stream
// to avoid unnecessary copying.
if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
do_QueryInterface(aUploadStream)) {
uint32_t count = multiplex->GetCount();
nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
nsTArray<RefPtr<GenericPromise>> promises(count);
bool replace = false;
for (uint32_t i = 0; i < count; ++i) {
nsCOMPtr<nsIInputStream> inner;
nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<GenericPromise> promise;
nsCOMPtr<nsIInputStream> replacement;
rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
getter_AddRefs(promise));
NS_ENSURE_SUCCESS(rv, rv);
if (promise) {
promises.AppendElement(promise);
}
if (replacement) {
streams.AppendElement(replacement);
replace = true;
} else {
streams.AppendElement(inner);
}
}
// If any of the inner streams needed to be replaced, replace the entire
// nsIMultiplexInputStream.
if (replace) {
nsresult rv;
nsCOMPtr<nsIMultiplexInputStream> replacement =
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
for (auto& stream : streams) {
rv = replacement->AppendStream(stream);
NS_ENSURE_SUCCESS(rv, rv);
}
MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
}
// Wait for all inner promises to settle before resolving the final promise.
if (!promises.IsEmpty()) {
RefPtr<GenericPromise> ready =
GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
->Then(GetCurrentSerialEventTarget(), __func__,
[](GenericPromise::AllSettledPromiseType::
ResolveOrRejectValue&& aResults)
-> RefPtr<GenericPromise> {
MOZ_ASSERT(aResults.IsResolve(),
"AllSettled never rejects");
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
return GenericPromise::CreateAndReject(
result.RejectValue(), __func__);
}
}
return GenericPromise::CreateAndResolve(true, __func__);
});
ready.forget(aReadyPromise);
}
return NS_OK; return NS_OK;
} }
// If the stream is cloneable, seekable and non-async, we can allow it. Async
// input streams can cause issues, as various consumers of input streams
// expect the payload to be synchronous and `Available()` to be the length of
// the stream, which is not true for asynchronous streams.
nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
return NS_OK;
}
// Asynchronously copy our non-normalized stream into a StorageStream so that
// it is seekable, cloneable, and synchronous once the copy completes.
NS_WARNING("Upload Stream is being copied into StorageStream");
nsCOMPtr<nsIStorageStream> storageStream; nsCOMPtr<nsIStorageStream> storageStream;
nsresult rv = nsresult rv =
NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream)); NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> newUploadStream;
rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> sink; nsCOMPtr<nsIOutputStream> sink;
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink)); rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> source; nsCOMPtr<nsIInputStream> replacementStream;
if (NS_InputStreamIsBuffered(mUploadStream)) { rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
source = mUploadStream; NS_ENSURE_SUCCESS(rv, rv);
} else {
rv = NS_NewBufferedInputStream(getter_AddRefs(source), // Ensure the source stream is buffered before starting the copy so we can use
mUploadStream.forget(), 4096); // ReadSegments, as nsStorageStream doesn't implement WriteSegments.
nsCOMPtr<nsIInputStream> source = aUploadStream;
if (!NS_InputStreamIsBuffered(aUploadStream)) {
nsCOMPtr<nsIInputStream> bufferedSource;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
source.forget(), 4096);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
source = bufferedSource.forget();
} }
// Perform an AsyncCopy into the input stream on the STS.
nsCOMPtr<nsIEventTarget> target = nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
mUploadCloneableCallback = aCallback; rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
NormalizeCopyComplete, do_AddRef(ready).take());
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
4096, // copy segment size
CopyComplete, this);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {
mUploadCloneableCallback = nullptr; ready.get()->Release();
return rv; return rv;
} }
// Since we're consuming the old stream, replace it with the new replacementStream.forget(aReplacementStream);
// stream immediately. ready.forget(aReadyPromise);
mUploadStream = newUploadStream;
// Explicity hold the stream alive until copying is complete. This will
// be released in EnsureUploadStreamIsCloneableComplete().
AddRef();
return NS_OK; return NS_OK;
} }
void HttpBaseChannel::OnCopyComplete(nsresult aStatus) { } // anonymous namespace
// Assert in parent process because we don't have to label the runnable
// in parent process.
MOZ_ASSERT(XRE_IsParentProcess());
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
"net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
&HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
NS_DispatchToMainThread(runnable.forget());
}
void HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus) {
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
MOZ_ASSERT(mUploadCloneableCallback);
if (NS_SUCCEEDED(mStatus)) {
mStatus = aStatus;
}
mUploadCloneableCallback->Run();
mUploadCloneableCallback = nullptr;
// Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
// that the copying is complete.
Release();
}
NS_IMETHODIMP NS_IMETHODIMP
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength, HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
@@ -992,6 +1136,11 @@ HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
NS_ENSURE_ARG_POINTER(aClonedStream); NS_ENSURE_ARG_POINTER(aClonedStream);
*aClonedStream = nullptr; *aClonedStream = nullptr;
if (!XRE_IsParentProcess()) {
NS_WARNING("CloneUploadStream is only supported in the parent process");
return NS_ERROR_NOT_AVAILABLE;
}
if (!mUploadStream) { if (!mUploadStream) {
return NS_OK; return NS_OK;
} }
@@ -1041,45 +1190,91 @@ HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
StoreUploadStreamHasHeaders(aStreamHasHeaders); StoreUploadStreamHasHeaders(aStreamHasHeaders);
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream); return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
if (!seekable) { }
nsCOMPtr<nsIInputStream> stream = aStream;
seekable = new PartiallySeekableInputStream(stream.forget());
}
mUploadStream = do_QueryInterface(seekable); nsresult HttpBaseChannel::InternalSetUploadStream(
nsIInputStream* aUploadStream, int64_t aContentLength,
bool aSetContentLengthHeader) {
// If we're not on the main thread, such as for TRR, the content length must
// be provided, as we can't normalize our upload stream.
if (!NS_IsMainThread()) {
if (aContentLength < 0) {
MOZ_ASSERT_UNREACHABLE(
"Upload content length must be explicit off-main-thread");
return NS_ERROR_INVALID_ARG;
}
if (aContentLength >= 0) { nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
ExplicitSetUploadStreamLength(aContentLength, aStreamHasHeaders); if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
MOZ_ASSERT_UNREACHABLE(
"Upload stream must be cloneable & seekable off-main-thread");
return NS_ERROR_INVALID_ARG;
}
mUploadStream = aUploadStream;
ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
return NS_OK; return NS_OK;
} }
// Sync access to the stream length. // Normalize the upload stream we're provided to ensure that it is cloneable,
int64_t length; // seekable, and synchronous when in the parent process.
if (InputStreamLengthHelper::GetSyncLength(aStream, &length)) { //
ExplicitSetUploadStreamLength(length >= 0 ? length : 0, aStreamHasHeaders); // This might be an async operation, in which case ready will be returned and
return NS_OK; // resolved when the operation is complete.
nsCOMPtr<nsIInputStream> replacement;
RefPtr<GenericPromise> ready;
if (XRE_IsParentProcess()) {
nsresult rv = NormalizeUploadStream(
aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
NS_ENSURE_SUCCESS(rv, rv);
} }
// Let's resolve the size of the stream. mUploadStream = replacement ? replacement.get() : aUploadStream;
RefPtr<HttpBaseChannel> self = this;
InputStreamLengthHelper::GetAsyncLength( // Once the upload stream is ready, fetch its length before proceeding with
aStream, [self, aStreamHasHeaders](int64_t aLength) { // AsyncOpen.
self->StorePendingInputStreamLengthOperation(false); auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0, stream = mUploadStream]() {
aStreamHasHeaders); auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
self->MaybeResumeAsyncOpen(); self->StorePendingUploadStreamNormalization(false);
}); self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
StorePendingInputStreamLengthOperation(true); aSetContentLengthHeader);
self->MaybeResumeAsyncOpen();
};
if (aContentLength >= 0) {
setLengthAndResume(aContentLength);
return;
}
int64_t length;
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
setLengthAndResume(length);
return;
}
InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
};
StorePendingUploadStreamNormalization(true);
// Resolve onReady synchronously unless a promise is returned.
if (ready) {
ready->Then(GetCurrentSerialEventTarget(), __func__,
[onReady = std::move(onReady)](
GenericPromise::ResolveOrRejectValue&&) { onReady(); });
} else {
onReady();
}
return NS_OK; return NS_OK;
} }
void HttpBaseChannel::ExplicitSetUploadStreamLength(uint64_t aContentLength, void HttpBaseChannel::ExplicitSetUploadStreamLength(
bool aStreamHasHeaders) { uint64_t aContentLength, bool aSetContentLengthHeader) {
// We already have the content length. We don't need to determinate it. // We already have the content length. We don't need to determinate it.
mReqContentLength = aContentLength; mReqContentLength = aContentLength;
if (aStreamHasHeaders) { if (!aSetContentLengthHeader) {
return; return;
} }
@@ -1108,33 +1303,33 @@ HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
return NS_OK; return NS_OK;
} }
bool HttpBaseChannel::MaybeWaitForUploadStreamLength( bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
nsIStreamListener* aListener, nsISupports* aContext) { nsIStreamListener* aListener, nsISupports* aContext) {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamLength(), MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
"AsyncOpen() called twice?"); "AsyncOpen() called twice?");
if (!LoadPendingInputStreamLengthOperation()) { if (!LoadPendingUploadStreamNormalization()) {
return false; return false;
} }
mListener = aListener; mListener = aListener;
StoreAsyncOpenWaitingForStreamLength(true); StoreAsyncOpenWaitingForStreamNormalization(true);
return true; return true;
} }
void HttpBaseChannel::MaybeResumeAsyncOpen() { void HttpBaseChannel::MaybeResumeAsyncOpen() {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!LoadPendingInputStreamLengthOperation()); MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
if (!LoadAsyncOpenWaitingForStreamLength()) { if (!LoadAsyncOpenWaitingForStreamNormalization()) {
return; return;
} }
nsCOMPtr<nsIStreamListener> listener; nsCOMPtr<nsIStreamListener> listener;
listener.swap(mListener); listener.swap(mListener);
StoreAsyncOpenWaitingForStreamLength(false); StoreAsyncOpenWaitingForStreamNormalization(false);
nsresult rv = AsyncOpen(listener); nsresult rv = AsyncOpen(listener);
if (NS_WARN_IF(NS_FAILED(rv))) { if (NS_WARN_IF(NS_FAILED(rv))) {

View File

@@ -452,11 +452,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
[[nodiscard]] nsresult DoApplyContentConversions( [[nodiscard]] nsresult DoApplyContentConversions(
nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener); nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener);
// Callback on STS thread called by CopyComplete when NS_AsyncCopy()
// is finished. This function works as a proxy function to dispatch
// |EnsureUploadStreamIsCloneableComplete| to main thread.
virtual void OnCopyComplete(nsresult aStatus);
void AddClassificationFlags(uint32_t aClassificationFlags, void AddClassificationFlags(uint32_t aClassificationFlags,
bool aIsThirdParty); bool aIsThirdParty);
@@ -464,13 +459,9 @@ class HttpBaseChannel : public nsHashPropertyBag,
const uint64_t& ChannelId() const { return mChannelId; } const uint64_t& ChannelId() const { return mChannelId; }
void InternalSetUploadStream(nsIInputStream* uploadStream) { nsresult InternalSetUploadStream(nsIInputStream* uploadStream,
mUploadStream = uploadStream; int64_t aContentLength = -1,
} bool aSetContentLengthHeader = false);
void InternalSetUploadStreamLength(uint64_t aLength) {
mReqContentLength = aLength;
}
void SetUploadStreamHasHeaders(bool hasHeaders) { void SetUploadStreamHasHeaders(bool hasHeaders) {
StoreUploadStreamHasHeaders(hasHeaders); StoreUploadStreamHasHeaders(hasHeaders);
@@ -594,10 +585,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
// prepare for a possible synthesized response instead. // prepare for a possible synthesized response instead.
bool ShouldIntercept(nsIURI* aURI = nullptr); bool ShouldIntercept(nsIURI* aURI = nullptr);
// Callback on main thread when NS_AsyncCopy() is finished populating
// the new mUploadStream.
void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
#ifdef DEBUG #ifdef DEBUG
// Check if mPrivateBrowsingId matches between LoadInfo and LoadContext. // Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
void AssertPrivateBrowsingId(); void AssertPrivateBrowsingId();
@@ -608,8 +595,8 @@ class HttpBaseChannel : public nsHashPropertyBag,
nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const; nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const;
bool MaybeWaitForUploadStreamLength(nsIStreamListener* aListener, bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener,
nsISupports* aContext); nsISupports* aContext);
void MaybeFlushConsoleReports(); void MaybeFlushConsoleReports();
@@ -662,7 +649,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
void ReleaseMainThreadOnlyReferences(); void ReleaseMainThreadOnlyReferences();
void ExplicitSetUploadStreamLength(uint64_t aContentLength, void ExplicitSetUploadStreamLength(uint64_t aContentLength,
bool aStreamHasHeaders); bool aSetContentLengthHeader);
void MaybeResumeAsyncOpen(); void MaybeResumeAsyncOpen();
@@ -698,7 +685,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
// Upload throttling. // Upload throttling.
nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue; nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
nsCOMPtr<nsIInputStream> mUploadStream; nsCOMPtr<nsIInputStream> mUploadStream;
nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
UniquePtr<nsHttpResponseHead> mResponseHead; UniquePtr<nsHttpResponseHead> mResponseHead;
UniquePtr<nsHttpHeaderArray> mResponseTrailers; UniquePtr<nsHttpHeaderArray> mResponseTrailers;
RefPtr<nsHttpConnectionInfo> mConnectionInfo; RefPtr<nsHttpConnectionInfo> mConnectionInfo;
@@ -844,10 +830,10 @@ class HttpBaseChannel : public nsHashPropertyBag,
// a non tail request. We must remove it again when this channel is done. // a non tail request. We must remove it again when this channel is done.
(uint32_t, AddedAsNonTailRequest, 1), (uint32_t, AddedAsNonTailRequest, 1),
// True if AsyncOpen() is called when the stream length is still unknown. // True if AsyncOpen() is called when the upload stream normalization or
// AsyncOpen() will be retriggered when InputStreamLengthHelper execs the // length is still unknown. AsyncOpen() will be retriggered when
// callback, passing the stream length value. // normalization is complete and length has been determined.
(uint32_t, AsyncOpenWaitingForStreamLength, 1), (uint32_t, AsyncOpenWaitingForStreamNormalization, 1),
// Defaults to true. This is set to false when it is no longer possible // Defaults to true. This is set to false when it is no longer possible
// to upgrade the request to a secure channel. // to upgrade the request to a secure channel.
@@ -947,9 +933,9 @@ class HttpBaseChannel : public nsHashPropertyBag,
(bool, DisableAltDataCache, 1), (bool, DisableAltDataCache, 1),
(bool, ForceMainDocumentChannel, 1), (bool, ForceMainDocumentChannel, 1),
// This is set true if the channel is waiting for the // This is set true if the channel is waiting for upload stream
// InputStreamLengthHelper::GetAsyncLength callback. // normalization or the InputStreamLengthHelper::GetAsyncLength callback.
(bool, PendingInputStreamLengthOperation, 1), (bool, PendingUploadStreamNormalization, 1),
// Set to true if our listener has indicated that it requires // Set to true if our listener has indicated that it requires
// content conversion to be done by us. // content conversion to be done by us.

View File

@@ -1965,7 +1965,7 @@ nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) {
NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
if (MaybeWaitForUploadStreamLength(listener, nullptr)) { if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
return NS_OK; return NS_OK;
} }
@@ -2833,16 +2833,6 @@ void HttpChannelChild::TrySendDeletingChannel() {
MOZ_ASSERT(NS_SUCCEEDED(rv)); MOZ_ASSERT(NS_SUCCEEDED(rv));
} }
void HttpChannelChild::OnCopyComplete(nsresult aStatus) {
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
"net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
&HttpChannelChild::EnsureUploadStreamIsCloneableComplete, aStatus);
nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget();
MOZ_ASSERT(neckoTarget);
Unused << neckoTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
}
nsresult HttpChannelChild::AsyncCallImpl( nsresult HttpChannelChild::AsyncCallImpl(
void (HttpChannelChild::*funcPtr)(), void (HttpChannelChild::*funcPtr)(),
nsRunnableMethod<HttpChannelChild>** retval) { nsRunnableMethod<HttpChannelChild>** retval) {

View File

@@ -113,8 +113,6 @@ class HttpChannelChild final : public PHttpChannelChild,
[[nodiscard]] bool IsSuspended(); [[nodiscard]] bool IsSuspended();
void OnCopyComplete(nsresult aStatus) override;
// Callback while background channel is ready. // Callback while background channel is ready.
void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild); void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
// Callback while background channel is destroyed. // Callback while background channel is destroyed.

View File

@@ -491,24 +491,11 @@ bool HttpChannelParent::DoAsyncOpen(
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream); nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
if (stream) { if (stream) {
int64_t length; rv = httpChannel->InternalSetUploadStream(stream);
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) { if (NS_FAILED(rv)) {
httpChannel->InternalSetUploadStreamLength(length >= 0 ? length : 0); return SendFailedAsyncOpen(rv);
} else {
// Wait for the nputStreamLengthHelper::GetAsyncLength callback.
++mAsyncOpenBarrier;
// Let's resolve the size of the stream. The following operation is always
// async.
RefPtr<HttpChannelParent> self = this;
InputStreamLengthHelper::GetAsyncLength(stream, [self, httpChannel](
int64_t aLength) {
httpChannel->InternalSetUploadStreamLength(aLength >= 0 ? aLength : 0);
self->TryInvokeAsyncOpen(NS_OK);
});
} }
httpChannel->InternalSetUploadStream(stream);
httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders); httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
} }
@@ -583,14 +570,6 @@ bool HttpChannelParent::DoAsyncOpen(
self->TryInvokeAsyncOpen(aStatus); self->TryInvokeAsyncOpen(aStatus);
}) })
->Track(mRequest); ->Track(mRequest);
// The stream, received from the child process, must be cloneable and seekable
// in order to allow devtools to inspect its content.
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction("HttpChannelParent::EnsureUploadStreamIsCloneable",
[self]() { self->TryInvokeAsyncOpen(NS_OK); });
++mAsyncOpenBarrier;
mChannel->EnsureUploadStreamIsCloneable(r);
return true; return true;
} }

View File

@@ -177,12 +177,12 @@ TRRServiceChannel::AsyncOpen(nsIStreamListener* aListener) {
return mStatus; return mStatus;
} }
// HttpBaseChannel::MaybeWaitForUploadStreamLength can only be used on main // HttpBaseChannel::MaybeWaitForUploadStreamNormalization can only be used on
// thread, so we can only return an error here. // main thread, so we can only return an error here.
#ifdef NIGHTLY_BUILD #ifdef NIGHTLY_BUILD
MOZ_ASSERT(!LoadPendingInputStreamLengthOperation()); MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
#endif #endif
if (LoadPendingInputStreamLengthOperation()) { if (LoadPendingUploadStreamNormalization()) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }

View File

@@ -5771,7 +5771,7 @@ nsHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
return NS_FAILED(mStatus) ? mStatus : NS_ERROR_FAILURE; return NS_FAILED(mStatus) ? mStatus : NS_ERROR_FAILURE;
} }
if (MaybeWaitForUploadStreamLength(listener, nullptr)) { if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
return NS_OK; return NS_OK;
} }

View File

@@ -10,13 +10,13 @@
* stream. * stream.
*/ */
[scriptable, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)] [scriptable, builtinclass, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)]
interface nsIMultiplexInputStream : nsISupports interface nsIMultiplexInputStream : nsISupports
{ {
/** /**
* Number of streams in this multiplex-stream * Number of streams in this multiplex-stream
*/ */
readonly attribute unsigned long count; [infallible] readonly attribute unsigned long count;
/** /**
* Appends a stream to the end of the streams. The cursor of the stream * Appends a stream to the end of the streams. The cursor of the stream