зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
725853b6d5
Коммит
e18a717abc
|
@ -2064,6 +2064,7 @@ void ServiceWorkerManager::DispatchFetchEvent(nsIInterceptedChannel* aChannel,
|
|||
ErrorResult& aRv) {
|
||||
MOZ_ASSERT(aChannel);
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(XRE_IsParentProcess());
|
||||
|
||||
nsCOMPtr<nsIChannel> 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
|
||||
// for the runnable. They should have arrived by now, but we still need to
|
||||
// wait for them if they have not.
|
||||
nsCOMPtr<nsIRunnable> permissionsRunnable = NS_NewRunnableFunction(
|
||||
"dom::ServiceWorkerManager::DispatchFetchEvent", [=]() {
|
||||
RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance();
|
||||
if (permMgr) {
|
||||
permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
|
||||
continueRunnable);
|
||||
} 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;
|
||||
RefPtr<PermissionManager> permMgr = PermissionManager::GetInstance();
|
||||
if (permMgr) {
|
||||
permMgr->WhenPermissionsAvailable(serviceWorker->Principal(),
|
||||
continueRunnable);
|
||||
} else {
|
||||
continueRunnable->HandleError();
|
||||
}
|
||||
// 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,
|
||||
|
|
|
@ -48,19 +48,7 @@ interface nsIUploadChannel2 : nsISupports
|
|||
readonly attribute boolean uploadStreamHasHeaders;
|
||||
|
||||
/**
|
||||
* Ensure the upload stream, if any, is cloneable. This may involve
|
||||
* 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.
|
||||
* Clones the upload stream. May only be called in the parent process.
|
||||
* aContentLength could be -1 in case the size of the stream is unknown,
|
||||
* otherwise it will contain the known size of the stream.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "HttpLog.h"
|
||||
#include "LoadInfo.h"
|
||||
#include "ReferrerInfo.h"
|
||||
#include "mozIRemoteLazyInputStream.h"
|
||||
#include "mozIThirdPartyUtil.h"
|
||||
#include "mozilla/AntiTrackingUtils.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
|
@ -41,9 +42,10 @@
|
|||
#include "mozilla/dom/ProcessIsolation.h"
|
||||
#include "mozilla/dom/WindowGlobalParent.h"
|
||||
#include "mozilla/net/OpaqueResponseUtils.h"
|
||||
#include "mozilla/net/PartiallySeekableInputStream.h"
|
||||
#include "mozilla/net/UrlClassifierCommon.h"
|
||||
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
||||
#include "nsBufferedStreams.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsContentSecurityManager.h"
|
||||
#include "nsContentSecurityUtils.h"
|
||||
|
@ -66,6 +68,7 @@
|
|||
#include "nsIHttpHeaderVisitor.h"
|
||||
#include "nsILoadGroupChild.h"
|
||||
#include "nsIMIMEInputStream.h"
|
||||
#include "nsIMultiplexInputStream.h"
|
||||
#include "nsIMutableArray.h"
|
||||
#include "nsINetworkInterceptController.h"
|
||||
#include "nsIObserverService.h"
|
||||
|
@ -866,13 +869,32 @@ HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
|
|||
// So we need special case for GET method.
|
||||
StoreUploadStreamHasHeaders(false);
|
||||
mRequestHead.SetMethod("GET"_ns); // revert to GET request
|
||||
mUploadStream = stream;
|
||||
mUploadStream = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
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
|
||||
// Called on the STS thread by NS_AsyncCopy
|
||||
nsCOMPtr<nsIEventTarget> sts =
|
||||
|
@ -882,108 +904,230 @@ void CopyComplete(void* aClosure, nsresult aStatus) {
|
|||
MOZ_ASSERT(result, "Should only be called on the STS thread.");
|
||||
#endif
|
||||
|
||||
auto* channel = static_cast<HttpBaseChannel*>(aClosure);
|
||||
channel->OnCopyComplete(aStatus);
|
||||
RefPtr<GenericPromise::Private> ready =
|
||||
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
|
||||
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback) {
|
||||
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
||||
NS_ENSURE_ARG_POINTER(aCallback);
|
||||
*aReplacementStream = nullptr;
|
||||
*aReadyPromise = nullptr;
|
||||
|
||||
// We could in theory allow multiple callers to use this method,
|
||||
// but the complexity does not seem worth it yet. Just fail if
|
||||
// this is called more than once simultaneously.
|
||||
NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
|
||||
// Unwrap RemoteLazyInputStream and normalize the contents as we're in the
|
||||
// parent process.
|
||||
if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
|
||||
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 (!mUploadStream) {
|
||||
aCallback->Run();
|
||||
if (replacement) {
|
||||
replacement.forget(aReplacementStream);
|
||||
} 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;
|
||||
}
|
||||
|
||||
// Upload nsIInputStream must be cloneable and seekable in order to be
|
||||
// processed by devtools network inspector.
|
||||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
|
||||
if (seekable && NS_InputStreamIsCloneable(mUploadStream)) {
|
||||
aCallback->Run();
|
||||
// Preserve "real" buffered input streams which wrap data (i.e. are backed by
|
||||
// nsBufferedInputStream), but normalize the wrapped stream.
|
||||
if (nsCOMPtr<nsIBufferedInputStream> buffered =
|
||||
do_QueryInterface(aUploadStream)) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
nsresult rv =
|
||||
NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> newUploadStream;
|
||||
rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIOutputStream> sink;
|
||||
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> source;
|
||||
if (NS_InputStreamIsBuffered(mUploadStream)) {
|
||||
source = mUploadStream;
|
||||
} else {
|
||||
rv = NS_NewBufferedInputStream(getter_AddRefs(source),
|
||||
mUploadStream.forget(), 4096);
|
||||
nsCOMPtr<nsIInputStream> replacementStream;
|
||||
rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Ensure the source stream is buffered before starting the copy so we can use
|
||||
// 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);
|
||||
source = bufferedSource.forget();
|
||||
}
|
||||
|
||||
// Perform an AsyncCopy into the input stream on the STS.
|
||||
nsCOMPtr<nsIEventTarget> target =
|
||||
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||||
|
||||
mUploadCloneableCallback = aCallback;
|
||||
|
||||
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
|
||||
4096, // copy segment size
|
||||
CopyComplete, this);
|
||||
RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
|
||||
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
|
||||
NormalizeCopyComplete, do_AddRef(ready).take());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mUploadCloneableCallback = nullptr;
|
||||
ready.get()->Release();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Since we're consuming the old stream, replace it with the new
|
||||
// stream immediately.
|
||||
mUploadStream = newUploadStream;
|
||||
|
||||
// Explicity hold the stream alive until copying is complete. This will
|
||||
// be released in EnsureUploadStreamIsCloneableComplete().
|
||||
AddRef();
|
||||
|
||||
replacementStream.forget(aReplacementStream);
|
||||
ready.forget(aReadyPromise);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void HttpBaseChannel::OnCopyComplete(nsresult aStatus) {
|
||||
// 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();
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
NS_IMETHODIMP
|
||||
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
|
||||
|
@ -992,6 +1136,11 @@ HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
|
|||
NS_ENSURE_ARG_POINTER(aClonedStream);
|
||||
*aClonedStream = nullptr;
|
||||
|
||||
if (!XRE_IsParentProcess()) {
|
||||
NS_WARNING("CloneUploadStream is only supported in the parent process");
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
if (!mUploadStream) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1041,45 +1190,91 @@ HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
|
|||
|
||||
StoreUploadStreamHasHeaders(aStreamHasHeaders);
|
||||
|
||||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
|
||||
if (!seekable) {
|
||||
nsCOMPtr<nsIInputStream> stream = aStream;
|
||||
seekable = new PartiallySeekableInputStream(stream.forget());
|
||||
}
|
||||
return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
|
||||
}
|
||||
|
||||
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) {
|
||||
ExplicitSetUploadStreamLength(aContentLength, aStreamHasHeaders);
|
||||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
|
||||
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;
|
||||
}
|
||||
|
||||
// Sync access to the stream length.
|
||||
int64_t length;
|
||||
if (InputStreamLengthHelper::GetSyncLength(aStream, &length)) {
|
||||
ExplicitSetUploadStreamLength(length >= 0 ? length : 0, aStreamHasHeaders);
|
||||
return NS_OK;
|
||||
// Normalize the upload stream we're provided to ensure that it is cloneable,
|
||||
// seekable, and synchronous when in the parent process.
|
||||
//
|
||||
// This might be an async operation, in which case ready will be returned and
|
||||
// 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.
|
||||
RefPtr<HttpBaseChannel> self = this;
|
||||
InputStreamLengthHelper::GetAsyncLength(
|
||||
aStream, [self, aStreamHasHeaders](int64_t aLength) {
|
||||
self->StorePendingInputStreamLengthOperation(false);
|
||||
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
|
||||
aStreamHasHeaders);
|
||||
self->MaybeResumeAsyncOpen();
|
||||
});
|
||||
StorePendingInputStreamLengthOperation(true);
|
||||
mUploadStream = replacement ? replacement.get() : aUploadStream;
|
||||
|
||||
// Once the upload stream is ready, fetch its length before proceeding with
|
||||
// AsyncOpen.
|
||||
auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
|
||||
stream = mUploadStream]() {
|
||||
auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
|
||||
self->StorePendingUploadStreamNormalization(false);
|
||||
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
|
||||
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;
|
||||
}
|
||||
|
||||
void HttpBaseChannel::ExplicitSetUploadStreamLength(uint64_t aContentLength,
|
||||
bool aStreamHasHeaders) {
|
||||
void HttpBaseChannel::ExplicitSetUploadStreamLength(
|
||||
uint64_t aContentLength, bool aSetContentLengthHeader) {
|
||||
// We already have the content length. We don't need to determinate it.
|
||||
mReqContentLength = aContentLength;
|
||||
|
||||
if (aStreamHasHeaders) {
|
||||
if (!aSetContentLengthHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1108,33 +1303,33 @@ HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
bool HttpBaseChannel::MaybeWaitForUploadStreamLength(
|
||||
bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
|
||||
nsIStreamListener* aListener, nsISupports* aContext) {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamLength(),
|
||||
MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
|
||||
"AsyncOpen() called twice?");
|
||||
|
||||
if (!LoadPendingInputStreamLengthOperation()) {
|
||||
if (!LoadPendingUploadStreamNormalization()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mListener = aListener;
|
||||
StoreAsyncOpenWaitingForStreamLength(true);
|
||||
StoreAsyncOpenWaitingForStreamNormalization(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpBaseChannel::MaybeResumeAsyncOpen() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!LoadPendingInputStreamLengthOperation());
|
||||
MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
|
||||
|
||||
if (!LoadAsyncOpenWaitingForStreamLength()) {
|
||||
if (!LoadAsyncOpenWaitingForStreamNormalization()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIStreamListener> listener;
|
||||
listener.swap(mListener);
|
||||
|
||||
StoreAsyncOpenWaitingForStreamLength(false);
|
||||
StoreAsyncOpenWaitingForStreamNormalization(false);
|
||||
|
||||
nsresult rv = AsyncOpen(listener);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
|
|
|
@ -452,11 +452,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
[[nodiscard]] nsresult DoApplyContentConversions(
|
||||
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,
|
||||
bool aIsThirdParty);
|
||||
|
||||
|
@ -464,13 +459,9 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
|
||||
const uint64_t& ChannelId() const { return mChannelId; }
|
||||
|
||||
void InternalSetUploadStream(nsIInputStream* uploadStream) {
|
||||
mUploadStream = uploadStream;
|
||||
}
|
||||
|
||||
void InternalSetUploadStreamLength(uint64_t aLength) {
|
||||
mReqContentLength = aLength;
|
||||
}
|
||||
nsresult InternalSetUploadStream(nsIInputStream* uploadStream,
|
||||
int64_t aContentLength = -1,
|
||||
bool aSetContentLengthHeader = false);
|
||||
|
||||
void SetUploadStreamHasHeaders(bool hasHeaders) {
|
||||
StoreUploadStreamHasHeaders(hasHeaders);
|
||||
|
@ -594,10 +585,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
// prepare for a possible synthesized response instead.
|
||||
bool ShouldIntercept(nsIURI* aURI = nullptr);
|
||||
|
||||
// Callback on main thread when NS_AsyncCopy() is finished populating
|
||||
// the new mUploadStream.
|
||||
void EnsureUploadStreamIsCloneableComplete(nsresult aStatus);
|
||||
|
||||
#ifdef DEBUG
|
||||
// Check if mPrivateBrowsingId matches between LoadInfo and LoadContext.
|
||||
void AssertPrivateBrowsingId();
|
||||
|
@ -608,8 +595,8 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
|
||||
nsresult CheckRedirectLimit(uint32_t aRedirectFlags) const;
|
||||
|
||||
bool MaybeWaitForUploadStreamLength(nsIStreamListener* aListener,
|
||||
nsISupports* aContext);
|
||||
bool MaybeWaitForUploadStreamNormalization(nsIStreamListener* aListener,
|
||||
nsISupports* aContext);
|
||||
|
||||
void MaybeFlushConsoleReports();
|
||||
|
||||
|
@ -662,7 +649,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
void ReleaseMainThreadOnlyReferences();
|
||||
|
||||
void ExplicitSetUploadStreamLength(uint64_t aContentLength,
|
||||
bool aStreamHasHeaders);
|
||||
bool aSetContentLengthHeader);
|
||||
|
||||
void MaybeResumeAsyncOpen();
|
||||
|
||||
|
@ -698,7 +685,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
// Upload throttling.
|
||||
nsCOMPtr<nsIInputChannelThrottleQueue> mThrottleQueue;
|
||||
nsCOMPtr<nsIInputStream> mUploadStream;
|
||||
nsCOMPtr<nsIRunnable> mUploadCloneableCallback;
|
||||
UniquePtr<nsHttpResponseHead> mResponseHead;
|
||||
UniquePtr<nsHttpHeaderArray> mResponseTrailers;
|
||||
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.
|
||||
(uint32_t, AddedAsNonTailRequest, 1),
|
||||
|
||||
// True if AsyncOpen() is called when the stream length is still unknown.
|
||||
// AsyncOpen() will be retriggered when InputStreamLengthHelper execs the
|
||||
// callback, passing the stream length value.
|
||||
(uint32_t, AsyncOpenWaitingForStreamLength, 1),
|
||||
// True if AsyncOpen() is called when the upload stream normalization or
|
||||
// length is still unknown. AsyncOpen() will be retriggered when
|
||||
// normalization is complete and length has been determined.
|
||||
(uint32_t, AsyncOpenWaitingForStreamNormalization, 1),
|
||||
|
||||
// Defaults to true. This is set to false when it is no longer possible
|
||||
// to upgrade the request to a secure channel.
|
||||
|
@ -947,9 +933,9 @@ class HttpBaseChannel : public nsHashPropertyBag,
|
|||
(bool, DisableAltDataCache, 1),
|
||||
|
||||
(bool, ForceMainDocumentChannel, 1),
|
||||
// This is set true if the channel is waiting for the
|
||||
// InputStreamLengthHelper::GetAsyncLength callback.
|
||||
(bool, PendingInputStreamLengthOperation, 1),
|
||||
// This is set true if the channel is waiting for upload stream
|
||||
// normalization or the InputStreamLengthHelper::GetAsyncLength callback.
|
||||
(bool, PendingUploadStreamNormalization, 1),
|
||||
|
||||
// Set to true if our listener has indicated that it requires
|
||||
// content conversion to be done by us.
|
||||
|
|
|
@ -1965,7 +1965,7 @@ nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) {
|
|||
NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS);
|
||||
NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED);
|
||||
|
||||
if (MaybeWaitForUploadStreamLength(listener, nullptr)) {
|
||||
if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -2833,16 +2833,6 @@ void HttpChannelChild::TrySendDeletingChannel() {
|
|||
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(
|
||||
void (HttpChannelChild::*funcPtr)(),
|
||||
nsRunnableMethod<HttpChannelChild>** retval) {
|
||||
|
|
|
@ -113,8 +113,6 @@ class HttpChannelChild final : public PHttpChannelChild,
|
|||
|
||||
[[nodiscard]] bool IsSuspended();
|
||||
|
||||
void OnCopyComplete(nsresult aStatus) override;
|
||||
|
||||
// Callback while background channel is ready.
|
||||
void OnBackgroundChildReady(HttpBackgroundChannelChild* aBgChild);
|
||||
// Callback while background channel is destroyed.
|
||||
|
|
|
@ -491,24 +491,11 @@ bool HttpChannelParent::DoAsyncOpen(
|
|||
|
||||
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
|
||||
if (stream) {
|
||||
int64_t length;
|
||||
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
|
||||
httpChannel->InternalSetUploadStreamLength(length >= 0 ? length : 0);
|
||||
} 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);
|
||||
});
|
||||
rv = httpChannel->InternalSetUploadStream(stream);
|
||||
if (NS_FAILED(rv)) {
|
||||
return SendFailedAsyncOpen(rv);
|
||||
}
|
||||
|
||||
httpChannel->InternalSetUploadStream(stream);
|
||||
httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
|
||||
}
|
||||
|
||||
|
@ -583,14 +570,6 @@ bool HttpChannelParent::DoAsyncOpen(
|
|||
self->TryInvokeAsyncOpen(aStatus);
|
||||
})
|
||||
->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;
|
||||
}
|
||||
|
||||
|
|
|
@ -177,12 +177,12 @@ TRRServiceChannel::AsyncOpen(nsIStreamListener* aListener) {
|
|||
return mStatus;
|
||||
}
|
||||
|
||||
// HttpBaseChannel::MaybeWaitForUploadStreamLength can only be used on main
|
||||
// thread, so we can only return an error here.
|
||||
// HttpBaseChannel::MaybeWaitForUploadStreamNormalization can only be used on
|
||||
// main thread, so we can only return an error here.
|
||||
#ifdef NIGHTLY_BUILD
|
||||
MOZ_ASSERT(!LoadPendingInputStreamLengthOperation());
|
||||
MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
|
||||
#endif
|
||||
if (LoadPendingInputStreamLengthOperation()) {
|
||||
if (LoadPendingUploadStreamNormalization()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
|
|
|
@ -5771,7 +5771,7 @@ nsHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
|
|||
return NS_FAILED(mStatus) ? mStatus : NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (MaybeWaitForUploadStreamLength(listener, nullptr)) {
|
||||
if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
* stream.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)]
|
||||
[scriptable, builtinclass, uuid(a076fd12-1dd1-11b2-b19a-d53b5dffaade)]
|
||||
interface nsIMultiplexInputStream : nsISupports
|
||||
{
|
||||
/**
|
||||
* 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
|
||||
|
|
Загрузка…
Ссылка в новой задаче